diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 7783128d6f..280630475b 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,45 +1,9 @@ --- name: Bug report -about: Create a report to help us improve GitHub Extension for Visual Studio +about: Create a report to help us improve the GitHub Extension for Visual Studio labels: --- - - -## Versions - - GitHub Extension for Visual Studio version: ... - - Visual Studio version: ... - -## What happened - - -### Steps to Reproduce -Steps to reproduce the behavior: -1. Go to '...' -2. Click on '....' -3. Scroll down to '....' -4. See error - -### Expected behavior - - -### Screenshots - - -### Logs - - -### Additional context - +Please report GitHub for Visual Studio bugs here: +https://support.github.com/contact/bug-report?subject=Re:%20GitHub%20for%20Visual%20Studio diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md deleted file mode 100644 index 751cb5de04..0000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -name: Feature request -about: Suggest an idea for this project -labels: - ---- - -**Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] - -**Describe the solution you'd like** -A clear and concise description of what you want to happen. - -**Describe alternatives you've considered** -A clear and concise description of any alternative solutions or features you've considered. - -**Additional context** -Add any other context or screenshots about the feature request here. diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000000..f6858ea3ee --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,83 @@ +# This is a basic workflow to help you get started with Actions + +name: CI + +env: + config: Release + githubvsSolution: GitHubVS.sln + vsixContainer: ${{ github.workspace }}\GitHub.VisualStudio.vsix + ACTIONS_ALLOW_UNSECURE_COMMANDS: true + +# Controls when the action will run. Triggers the workflow on push or pull request +# events but only for the master branch +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: windows-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - uses: actions/checkout@v1 + with: + submodules: true + + - name: Nerdbank.GitVersioning + uses: aarnott/nbgv@v0.3 + id: nbgv + + - name: Add MSBuild to PATH + uses: microsoft/setup-msbuild@v1.0.0 + + - uses: nuget/setup-nuget@v1 + with: + nuget-version: '5.x' + + - name: Add VSTest to PATH + uses: darenm/Setup-VSTest@v1 + + - name: NuGet restore ${{ env.githubvsSolution }} + run: nuget restore ${{ env.githubvsSolution }} + + - name: MSBuild ${{ env.githubvsSolution }} + run: | + msbuild ${{ env.githubvsSolution }} /p:Configuration=${{ env.config }} /p:TargetVsixContainer=${{ env.vsixContainer }} /p:DeployExtension=False /verbosity:minimal + env: + GitHubVS_ClientId: ${{ secrets.GitHubVS_ClientId }} + GitHubVS_ClientSecret: ${{ secrets.GitHubVS_ClientSecret }} + + - name: Sign the VSIX + if: github.ref == 'refs/heads/master' + run: tools/vsixsigntool/vsixsigntool.exe sign /f certificate.pfx /p '${{ secrets.CERTIFICATE_PASSWORD }}' /sha1 9c5a6d389e1454f2ed9ee9419cdf743689709f9c /fd sha256 /tr http://timestamp.digicert.com /td sha256 ${{ env.vsixContainer }} + + - name: Upload VSIX artifact + uses: actions/upload-artifact@v1 + with: + name: GitHubVS-${{ steps.nbgv.outputs.SemVer2 }} + path: ${{ env.vsixContainer }} + + # We need to run '**\bin\**\*Tests.dll' + - name: Run unit tests + shell: bash + run: | + vstest.console /TestAdapterPath:test /Settings:test/test.runsettings \ + test/GitHub.Api.UnitTests/bin/${{ env.config }}/net46/GitHub.Api.UnitTests.dll \ + test/GitHub.App.UnitTests/bin/${{ env.config }}/net46/GitHub.App.UnitTests.dll \ + test/GitHub.Exports.Reactive.UnitTests/bin/${{ env.config }}/net46/GitHub.Exports.Reactive.UnitTests.dll \ + test/GitHub.Exports.UnitTests/bin/${{ env.config }}/net46/GitHub.Exports.UnitTests.dll \ + test/GitHub.Extensions.UnitTests/bin/${{ env.config }}/net46/GitHub.Extensions.UnitTests.dll \ + test/GitHub.InlineReviews.UnitTests/bin/${{ env.config }}/net46/GitHub.InlineReviews.UnitTests.dll \ + test/GitHub.Services.Vssdk.UnitTests/bin/${{ env.config }}/net461/GitHub.Services.Vssdk.UnitTests.dll \ + test/GitHub.StartPage.UnitTests/bin/${{ env.config }}/net46/GitHub.StartPage.UnitTests.dll \ + test/GitHub.TeamFoundation.UnitTests/bin/${{ env.config }}/net46/GitHub.TeamFoundation.UnitTests.dll \ + test/GitHub.UI.UnitTests/bin/${{ env.config }}/net46/GitHub.UI.UnitTests.dll \ + test/GitHub.VisualStudio.UnitTests/bin/${{ env.config }}/net46/GitHub.VisualStudio.UnitTests.dll \ + test/MetricsTests/MetricsTests/bin/${{ env.config }}/MetricsTests.dll diff --git a/.gitignore b/.gitignore index dc32f49486..e7cf555bf3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,9 +26,10 @@ build/ [Tt]est[Rr]esult*/ [Bb]uild[Ll]og.* -#NUNIT +# NUnit *.VisualState.xml TestResult.xml +nunit-*.xml # Build Results of an ATL Project [Dd]ebugPS/ @@ -238,8 +239,14 @@ coverage.xml #Msbuild binary log output output.binlog +# KDiff3 +*_BACKUP_* +*_BASE_* +*_LOCAL_* +*_REMOTE_* +*.orig + AkavacheSqliteLinkerOverride.cs NuGetBuild WiX.Toolset.DummyFile.txt -nunit-*.xml GitHubVS.sln.DotSettings diff --git a/.gitmodules b/.gitmodules index f37964cd09..f7c8b36bf5 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,6 +10,6 @@ [submodule "submodules/akavache"] path = submodules/akavache url = https://github.com/editor-tools/Akavache -[submodule "script"] - path = script - url = git@github.com:github/VisualStudioBuildScripts \ No newline at end of file +[submodule "submodules/octokit.graphql.net"] + path = submodules/octokit.graphql.net + url = https://github.com/octokit/octokit.graphql.net diff --git a/Directory.Build.Props b/Directory.Build.Props index a741d0a358..820c5c34da 100644 --- a/Directory.Build.Props +++ b/Directory.Build.Props @@ -1,8 +1,14 @@ GitHub Extension for Visual Studio - 2.6.0.0 + 2.10.8.0 Copyright © GitHub, Inc. 2014-2018 7.3 - + + + 3.0.26 + all + + + \ No newline at end of file diff --git a/GitHubVS.sln b/GitHubVS.sln index 2591ba9d7b..03f0b02a4c 100644 --- a/GitHubVS.sln +++ b/GitHubVS.sln @@ -1,8 +1,10 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.27703.2035 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28603.18 MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.VisualStudio.Vsix", "src\GitHub.VisualStudio.Vsix\GitHub.VisualStudio.Vsix.csproj", "{D26B4B40-0C94-48AD-8019-0B9BE46E0071}" +EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.VisualStudio", "src\GitHub.VisualStudio\GitHub.VisualStudio.csproj", "{11569514-5AE5-4B5B-92A2-F10B0967DE5F}" ProjectSection(ProjectDependencies) = postProject {7F5ED78B-74A3-4406-A299-70CFB5885B8B} = {7F5ED78B-74A3-4406-A299-70CFB5885B8B} @@ -11,7 +13,9 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Meta", "Meta", "{72036B62-2FA6-4A22-8B33-69F698A18CF1}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig + .github\workflows\main.yml = .github\workflows\main.yml README.md = README.md + version.json = version.json EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitHub.UI", "src\GitHub.UI\GitHub.UI.csproj", "{346384DD-2445-4A28-AF22-B45F3957BD89}" @@ -29,32 +33,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Submodules", "Submodules", test\UnitTests\Args.cs = test\UnitTests\Args.cs EndProjectSection EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Modules", "Modules", "{8E1F1B4E-AEA2-4AB1-8F73-423A903550A1}" - ProjectSection(SolutionItems) = preProject - scripts\Modules\BuildUtils.psm1 = scripts\Modules\BuildUtils.psm1 - scripts\Modules\Debugging.psm1 = scripts\Modules\Debugging.psm1 - scripts\Modules\Vsix.psm1 = scripts\Modules\Vsix.psm1 - EndProjectSection -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Scripts", "Scripts", "{7B6C5F8D-14B3-443D-B044-0E209AE12BDF}" - ProjectSection(SolutionItems) = preProject - .gitattributes = .gitattributes - .gitignore = .gitignore - appveyor.yml = appveyor.yml - scripts\build.ps1 = scripts\build.ps1 - scripts\Bump-Version.ps1 = scripts\Bump-Version.ps1 - scripts\common.ps1 = scripts\common.ps1 - scripts\Get-CheckedOutBranch.ps1 = scripts\Get-CheckedOutBranch.ps1 - scripts\Get-HeadSha1.ps1 = scripts\Get-HeadSha1.ps1 - scripts\modules.ps1 = scripts\modules.ps1 - nuget.config = nuget.config - scripts\Require-CleanWorkTree.ps1 = scripts\Require-CleanWorkTree.ps1 - scripts\Run-CodeCoverage.ps1 = scripts\Run-CodeCoverage.ps1 - scripts\Run-NUnit.ps1 = scripts\Run-NUnit.ps1 - scripts\Run-Tests.ps1 = scripts\Run-Tests.ps1 - scripts\Run-XUnit.ps1 = scripts\Run-XUnit.ps1 - EndProjectSection -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{8A7DA2E7-262B-4581-807A-1C45CE79CDFD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitHub.Exports", "src\GitHub.Exports\GitHub.Exports.csproj", "{9AEA02DB-02B5-409C-B0CA-115D05331A6B}" @@ -135,19 +113,32 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InstallAndStart", "test\Lau EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GitHub.TeamFoundation.16", "src\GitHub.TeamFoundation.16\GitHub.TeamFoundation.16.csproj", "{F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitHub.Services.Vssdk.UnitTests", "test\GitHub.Services.Vssdk.UnitTests\GitHub.Services.Vssdk.UnitTests.csproj", "{65542DEE-D3BE-4810-B85A-08E970413A21}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Octokit.GraphQL.Core", "submodules\octokit.graphql.net\Octokit.GraphQL.Core\Octokit.GraphQL.Core.csproj", "{3321CE72-26ED-4D1E-A8F5-6901FB783007}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Octokit.GraphQL", "submodules\octokit.graphql.net\Octokit.GraphQL\Octokit.GraphQL.csproj", "{791B408C-0ABC-465B-9EB1-A2422D67F418}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GitHub.StartPage.UnitTests", "test\GitHub.StartPage.UnitTests\GitHub.StartPage.UnitTests.csproj", "{B467682B-9F0E-42D8-8A20-1DE78F798793}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU - DebugCodeAnalysis|Any CPU = DebugCodeAnalysis|Any CPU DebugWithoutVsix|Any CPU = DebugWithoutVsix|Any CPU Release|Any CPU = Release|Any CPU ReleaseWithoutVsix|Any CPU = ReleaseWithoutVsix|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D26B4B40-0C94-48AD-8019-0B9BE46E0071}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D26B4B40-0C94-48AD-8019-0B9BE46E0071}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D26B4B40-0C94-48AD-8019-0B9BE46E0071}.DebugWithoutVsix|Any CPU.ActiveCfg = DebugWithoutVsix|Any CPU + {D26B4B40-0C94-48AD-8019-0B9BE46E0071}.DebugWithoutVsix|Any CPU.Build.0 = DebugWithoutVsix|Any CPU + {D26B4B40-0C94-48AD-8019-0B9BE46E0071}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D26B4B40-0C94-48AD-8019-0B9BE46E0071}.Release|Any CPU.Build.0 = Release|Any CPU + {D26B4B40-0C94-48AD-8019-0B9BE46E0071}.ReleaseWithoutVsix|Any CPU.ActiveCfg = ReleaseWithoutVsix|Any CPU + {D26B4B40-0C94-48AD-8019-0B9BE46E0071}.ReleaseWithoutVsix|Any CPU.Build.0 = ReleaseWithoutVsix|Any CPU {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU - {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|Any CPU {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.DebugWithoutVsix|Any CPU.ActiveCfg = DebugWithoutVsix|Any CPU {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.DebugWithoutVsix|Any CPU.Build.0 = DebugWithoutVsix|Any CPU {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -156,8 +147,6 @@ Global {11569514-5AE5-4B5B-92A2-F10B0967DE5F}.ReleaseWithoutVsix|Any CPU.Build.0 = ReleaseWithoutVsix|Any CPU {346384DD-2445-4A28-AF22-B45F3957BD89}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {346384DD-2445-4A28-AF22-B45F3957BD89}.Debug|Any CPU.Build.0 = Debug|Any CPU - {346384DD-2445-4A28-AF22-B45F3957BD89}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {346384DD-2445-4A28-AF22-B45F3957BD89}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {346384DD-2445-4A28-AF22-B45F3957BD89}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {346384DD-2445-4A28-AF22-B45F3957BD89}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {346384DD-2445-4A28-AF22-B45F3957BD89}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -166,8 +155,6 @@ Global {346384DD-2445-4A28-AF22-B45F3957BD89}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -176,8 +163,6 @@ Global {158B05E8-FDBC-4D71-B871-C96E28D5ADF5}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -186,8 +171,6 @@ Global {6AFE2E2D-6DB0-4430-A2EA-F5F5388D2F78}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6559E128-8B40-49A5-85A8-05565ED0C7E3}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {6559E128-8B40-49A5-85A8-05565ED0C7E3}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {6559E128-8B40-49A5-85A8-05565ED0C7E3}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {6559E128-8B40-49A5-85A8-05565ED0C7E3}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {6559E128-8B40-49A5-85A8-05565ED0C7E3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -196,8 +179,6 @@ Global {6559E128-8B40-49A5-85A8-05565ED0C7E3}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -206,8 +187,6 @@ Global {1A1DA411-8D1F-4578-80A6-04576BEA2DC5}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -216,8 +195,6 @@ Global {9AEA02DB-02B5-409C-B0CA-115D05331A6B}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B389ADAF-62CC-486E-85B4-2D8B078DF763}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {B389ADAF-62CC-486E-85B4-2D8B078DF763}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {B389ADAF-62CC-486E-85B4-2D8B078DF763}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {B389ADAF-62CC-486E-85B4-2D8B078DF763}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {B389ADAF-62CC-486E-85B4-2D8B078DF763}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -226,8 +203,6 @@ Global {B389ADAF-62CC-486E-85B4-2D8B078DF763}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -236,8 +211,6 @@ Global {E4ED0537-D1D9-44B6-9212-3096D7C3F7A1}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {08DD4305-7787-4823-A53F-4D0F725A07F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {08DD4305-7787-4823-A53F-4D0F725A07F3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.DebugCodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {08DD4305-7787-4823-A53F-4D0F725A07F3}.DebugCodeAnalysis|Any CPU.Build.0 = Release|Any CPU {08DD4305-7787-4823-A53F-4D0F725A07F3}.DebugWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU {08DD4305-7787-4823-A53F-4D0F725A07F3}.DebugWithoutVsix|Any CPU.Build.0 = Release|Any CPU {08DD4305-7787-4823-A53F-4D0F725A07F3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -246,8 +219,6 @@ Global {08DD4305-7787-4823-A53F-4D0F725A07F3}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Debug|Any CPU.ActiveCfg = Release|Any CPU {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Debug|Any CPU.Build.0 = Release|Any CPU - {674B69B8-0780-4D54-AE2B-C15821FA51CB}.DebugCodeAnalysis|Any CPU.ActiveCfg = Release|Any CPU - {674B69B8-0780-4D54-AE2B-C15821FA51CB}.DebugCodeAnalysis|Any CPU.Build.0 = Release|Any CPU {674B69B8-0780-4D54-AE2B-C15821FA51CB}.DebugWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU {674B69B8-0780-4D54-AE2B-C15821FA51CB}.DebugWithoutVsix|Any CPU.Build.0 = Release|Any CPU {674B69B8-0780-4D54-AE2B-C15821FA51CB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -256,8 +227,6 @@ Global {674B69B8-0780-4D54-AE2B-C15821FA51CB}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -266,8 +235,6 @@ Global {41A47C5B-C606-45B4-B83C-22B9239E4DA0}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {161DBF01-1DBF-4B00-8551-C5C00F26720D}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU - {161DBF01-1DBF-4B00-8551-C5C00F26720D}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720D}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720D}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720D}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -276,8 +243,6 @@ Global {161DBF01-1DBF-4B00-8551-C5C00F26720D}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {161DBF01-1DBF-4B00-8551-C5C00F26720E}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU - {161DBF01-1DBF-4B00-8551-C5C00F26720E}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720E}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720E}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {161DBF01-1DBF-4B00-8551-C5C00F26720E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -286,8 +251,6 @@ Global {161DBF01-1DBF-4B00-8551-C5C00F26720E}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -296,8 +259,6 @@ Global {D1DFBB0C-B570-4302-8F1E-2E3A19C41961}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.Debug|Any CPU.Build.0 = Debug|Any CPU - {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU - {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|Any CPU {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -306,8 +267,6 @@ Global {50E277B8-8580-487A-8F8E-5C3B9FBF0F77}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {110B206F-8554-4B51-BF86-94DAA32F5E26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {110B206F-8554-4B51-BF86-94DAA32F5E26}.Debug|Any CPU.Build.0 = Debug|Any CPU - {110B206F-8554-4B51-BF86-94DAA32F5E26}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {110B206F-8554-4B51-BF86-94DAA32F5E26}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {110B206F-8554-4B51-BF86-94DAA32F5E26}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {110B206F-8554-4B51-BF86-94DAA32F5E26}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {110B206F-8554-4B51-BF86-94DAA32F5E26}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -316,8 +275,6 @@ Global {110B206F-8554-4B51-BF86-94DAA32F5E26}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU - {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|Any CPU {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -326,8 +283,6 @@ Global {7F5ED78B-74A3-4406-A299-70CFB5885B8B}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {17EB676B-BB91-48B5-AA59-C67695C647C2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {17EB676B-BB91-48B5-AA59-C67695C647C2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17EB676B-BB91-48B5-AA59-C67695C647C2}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {17EB676B-BB91-48B5-AA59-C67695C647C2}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {17EB676B-BB91-48B5-AA59-C67695C647C2}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {17EB676B-BB91-48B5-AA59-C67695C647C2}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {17EB676B-BB91-48B5-AA59-C67695C647C2}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -336,8 +291,6 @@ Global {17EB676B-BB91-48B5-AA59-C67695C647C2}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {8D73575A-A89F-47CC-B153-B47DD06837F0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8D73575A-A89F-47CC-B153-B47DD06837F0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8D73575A-A89F-47CC-B153-B47DD06837F0}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {8D73575A-A89F-47CC-B153-B47DD06837F0}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {8D73575A-A89F-47CC-B153-B47DD06837F0}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {8D73575A-A89F-47CC-B153-B47DD06837F0}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {8D73575A-A89F-47CC-B153-B47DD06837F0}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -346,8 +299,6 @@ Global {8D73575A-A89F-47CC-B153-B47DD06837F0}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -356,8 +307,6 @@ Global {2D3D2834-33BE-45CA-B3CC-12F853557D7B}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {14FDEE91-7301-4247-846C-049647BF8E99}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {14FDEE91-7301-4247-846C-049647BF8E99}.Debug|Any CPU.Build.0 = Debug|Any CPU - {14FDEE91-7301-4247-846C-049647BF8E99}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {14FDEE91-7301-4247-846C-049647BF8E99}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {14FDEE91-7301-4247-846C-049647BF8E99}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {14FDEE91-7301-4247-846C-049647BF8E99}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {14FDEE91-7301-4247-846C-049647BF8E99}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -366,8 +315,6 @@ Global {14FDEE91-7301-4247-846C-049647BF8E99}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -376,8 +323,6 @@ Global {09313E65-7ADB-48C1-AD3A-572020C5BDCB}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {EFDE0798-ACDB-431D-B7F1-548A7231C853}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {EFDE0798-ACDB-431D-B7F1-548A7231C853}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EFDE0798-ACDB-431D-B7F1-548A7231C853}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {EFDE0798-ACDB-431D-B7F1-548A7231C853}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {EFDE0798-ACDB-431D-B7F1-548A7231C853}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {EFDE0798-ACDB-431D-B7F1-548A7231C853}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {EFDE0798-ACDB-431D-B7F1-548A7231C853}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -386,8 +331,6 @@ Global {EFDE0798-ACDB-431D-B7F1-548A7231C853}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {3525D819-6AEC-4879-89FB-56B41F026571}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {3525D819-6AEC-4879-89FB-56B41F026571}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3525D819-6AEC-4879-89FB-56B41F026571}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {3525D819-6AEC-4879-89FB-56B41F026571}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {3525D819-6AEC-4879-89FB-56B41F026571}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {3525D819-6AEC-4879-89FB-56B41F026571}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {3525D819-6AEC-4879-89FB-56B41F026571}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -396,8 +339,6 @@ Global {3525D819-6AEC-4879-89FB-56B41F026571}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -406,8 +347,6 @@ Global {94509FCB-6C97-4ED6-AED6-6E74AB3CA336}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -416,8 +355,6 @@ Global {C59868FC-D8BC-4D47-B4F3-16908D2641C6}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -426,8 +363,6 @@ Global {DE704BBB-6EC6-4173-B695-D9EBF5AEB092}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {93778A89-3E58-4853-B772-948EBB3F17BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {93778A89-3E58-4853-B772-948EBB3F17BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {93778A89-3E58-4853-B772-948EBB3F17BE}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {93778A89-3E58-4853-B772-948EBB3F17BE}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {93778A89-3E58-4853-B772-948EBB3F17BE}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {93778A89-3E58-4853-B772-948EBB3F17BE}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {93778A89-3E58-4853-B772-948EBB3F17BE}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -436,8 +371,6 @@ Global {93778A89-3E58-4853-B772-948EBB3F17BE}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {8B14F90B-0781-465D-AB94-19C8C56E3A94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {8B14F90B-0781-465D-AB94-19C8C56E3A94}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8B14F90B-0781-465D-AB94-19C8C56E3A94}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {8B14F90B-0781-465D-AB94-19C8C56E3A94}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {8B14F90B-0781-465D-AB94-19C8C56E3A94}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {8B14F90B-0781-465D-AB94-19C8C56E3A94}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {8B14F90B-0781-465D-AB94-19C8C56E3A94}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -446,8 +379,6 @@ Global {8B14F90B-0781-465D-AB94-19C8C56E3A94}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {54E8D71A-AABB-4698-95FE-7F11612B8E59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {54E8D71A-AABB-4698-95FE-7F11612B8E59}.Debug|Any CPU.Build.0 = Debug|Any CPU - {54E8D71A-AABB-4698-95FE-7F11612B8E59}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {54E8D71A-AABB-4698-95FE-7F11612B8E59}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {54E8D71A-AABB-4698-95FE-7F11612B8E59}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {54E8D71A-AABB-4698-95FE-7F11612B8E59}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {54E8D71A-AABB-4698-95FE-7F11612B8E59}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -456,8 +387,6 @@ Global {54E8D71A-AABB-4698-95FE-7F11612B8E59}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {9E17369D-DA49-48C1-9767-C5178A17BFB8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {9E17369D-DA49-48C1-9767-C5178A17BFB8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E17369D-DA49-48C1-9767-C5178A17BFB8}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {9E17369D-DA49-48C1-9767-C5178A17BFB8}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {9E17369D-DA49-48C1-9767-C5178A17BFB8}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {9E17369D-DA49-48C1-9767-C5178A17BFB8}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {9E17369D-DA49-48C1-9767-C5178A17BFB8}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -466,8 +395,6 @@ Global {9E17369D-DA49-48C1-9767-C5178A17BFB8}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {A003B735-6F6C-4DF8-A663-78651A1B6CE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A003B735-6F6C-4DF8-A663-78651A1B6CE0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A003B735-6F6C-4DF8-A663-78651A1B6CE0}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {A003B735-6F6C-4DF8-A663-78651A1B6CE0}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {A003B735-6F6C-4DF8-A663-78651A1B6CE0}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {A003B735-6F6C-4DF8-A663-78651A1B6CE0}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {A003B735-6F6C-4DF8-A663-78651A1B6CE0}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -476,8 +403,6 @@ Global {A003B735-6F6C-4DF8-A663-78651A1B6CE0}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {A4F579F3-77D3-450A-AACC-F2653EF11E69}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A4F579F3-77D3-450A-AACC-F2653EF11E69}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4F579F3-77D3-450A-AACC-F2653EF11E69}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {A4F579F3-77D3-450A-AACC-F2653EF11E69}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {A4F579F3-77D3-450A-AACC-F2653EF11E69}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {A4F579F3-77D3-450A-AACC-F2653EF11E69}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {A4F579F3-77D3-450A-AACC-F2653EF11E69}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -486,8 +411,6 @@ Global {A4F579F3-77D3-450A-AACC-F2653EF11E69}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {AD0306B7-F88E-44A4-AB36-1D04822E9234}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {AD0306B7-F88E-44A4-AB36-1D04822E9234}.Debug|Any CPU.Build.0 = Debug|Any CPU - {AD0306B7-F88E-44A4-AB36-1D04822E9234}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {AD0306B7-F88E-44A4-AB36-1D04822E9234}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {AD0306B7-F88E-44A4-AB36-1D04822E9234}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {AD0306B7-F88E-44A4-AB36-1D04822E9234}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {AD0306B7-F88E-44A4-AB36-1D04822E9234}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -496,8 +419,6 @@ Global {AD0306B7-F88E-44A4-AB36-1D04822E9234}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {86C54B27-717F-478C-AC8C-01F1C68A56C5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {86C54B27-717F-478C-AC8C-01F1C68A56C5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {86C54B27-717F-478C-AC8C-01F1C68A56C5}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {86C54B27-717F-478C-AC8C-01F1C68A56C5}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {86C54B27-717F-478C-AC8C-01F1C68A56C5}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {86C54B27-717F-478C-AC8C-01F1C68A56C5}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {86C54B27-717F-478C-AC8C-01F1C68A56C5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -506,8 +427,6 @@ Global {86C54B27-717F-478C-AC8C-01F1C68A56C5}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -516,8 +435,6 @@ Global {C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {E899B03C-6E8E-4375-AB65-FC925D721D8B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {E899B03C-6E8E-4375-AB65-FC925D721D8B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E899B03C-6E8E-4375-AB65-FC925D721D8B}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {E899B03C-6E8E-4375-AB65-FC925D721D8B}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {E899B03C-6E8E-4375-AB65-FC925D721D8B}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {E899B03C-6E8E-4375-AB65-FC925D721D8B}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {E899B03C-6E8E-4375-AB65-FC925D721D8B}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -526,8 +443,6 @@ Global {E899B03C-6E8E-4375-AB65-FC925D721D8B}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {79F32BE1-2764-4DBA-97F6-21053DE44270}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {79F32BE1-2764-4DBA-97F6-21053DE44270}.Debug|Any CPU.Build.0 = Debug|Any CPU - {79F32BE1-2764-4DBA-97F6-21053DE44270}.DebugCodeAnalysis|Any CPU.ActiveCfg = Debug|Any CPU - {79F32BE1-2764-4DBA-97F6-21053DE44270}.DebugCodeAnalysis|Any CPU.Build.0 = Debug|Any CPU {79F32BE1-2764-4DBA-97F6-21053DE44270}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU {79F32BE1-2764-4DBA-97F6-21053DE44270}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU {79F32BE1-2764-4DBA-97F6-21053DE44270}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -536,20 +451,49 @@ Global {79F32BE1-2764-4DBA-97F6-21053DE44270}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.DebugCodeAnalysis|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU - {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.DebugCodeAnalysis|Any CPU.Build.0 = DebugCodeAnalysis|Any CPU {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.DebugWithoutVsix|Any CPU.ActiveCfg = DebugCodeAnalysis|Any CPU {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.DebugWithoutVsix|Any CPU.Build.0 = DebugCodeAnalysis|Any CPU {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.Release|Any CPU.ActiveCfg = Release|Any CPU {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.Release|Any CPU.Build.0 = Release|Any CPU {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU {F08BD4BC-B5DF-4193-9B01-6D0BBE101BD7}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU + {65542DEE-D3BE-4810-B85A-08E970413A21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {65542DEE-D3BE-4810-B85A-08E970413A21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {65542DEE-D3BE-4810-B85A-08E970413A21}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU + {65542DEE-D3BE-4810-B85A-08E970413A21}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU + {65542DEE-D3BE-4810-B85A-08E970413A21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {65542DEE-D3BE-4810-B85A-08E970413A21}.Release|Any CPU.Build.0 = Release|Any CPU + {65542DEE-D3BE-4810-B85A-08E970413A21}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {65542DEE-D3BE-4810-B85A-08E970413A21}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU + {3321CE72-26ED-4D1E-A8F5-6901FB783007}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3321CE72-26ED-4D1E-A8F5-6901FB783007}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3321CE72-26ED-4D1E-A8F5-6901FB783007}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU + {3321CE72-26ED-4D1E-A8F5-6901FB783007}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU + {3321CE72-26ED-4D1E-A8F5-6901FB783007}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3321CE72-26ED-4D1E-A8F5-6901FB783007}.Release|Any CPU.Build.0 = Release|Any CPU + {3321CE72-26ED-4D1E-A8F5-6901FB783007}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {3321CE72-26ED-4D1E-A8F5-6901FB783007}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU + {791B408C-0ABC-465B-9EB1-A2422D67F418}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {791B408C-0ABC-465B-9EB1-A2422D67F418}.Debug|Any CPU.Build.0 = Debug|Any CPU + {791B408C-0ABC-465B-9EB1-A2422D67F418}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU + {791B408C-0ABC-465B-9EB1-A2422D67F418}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU + {791B408C-0ABC-465B-9EB1-A2422D67F418}.Release|Any CPU.ActiveCfg = Release|Any CPU + {791B408C-0ABC-465B-9EB1-A2422D67F418}.Release|Any CPU.Build.0 = Release|Any CPU + {791B408C-0ABC-465B-9EB1-A2422D67F418}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {791B408C-0ABC-465B-9EB1-A2422D67F418}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU + {B467682B-9F0E-42D8-8A20-1DE78F798793}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B467682B-9F0E-42D8-8A20-1DE78F798793}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B467682B-9F0E-42D8-8A20-1DE78F798793}.DebugWithoutVsix|Any CPU.ActiveCfg = Debug|Any CPU + {B467682B-9F0E-42D8-8A20-1DE78F798793}.DebugWithoutVsix|Any CPU.Build.0 = Debug|Any CPU + {B467682B-9F0E-42D8-8A20-1DE78F798793}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B467682B-9F0E-42D8-8A20-1DE78F798793}.Release|Any CPU.Build.0 = Release|Any CPU + {B467682B-9F0E-42D8-8A20-1DE78F798793}.ReleaseWithoutVsix|Any CPU.ActiveCfg = Release|Any CPU + {B467682B-9F0E-42D8-8A20-1DE78F798793}.ReleaseWithoutVsix|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {8E1F1B4E-AEA2-4AB1-8F73-423A903550A1} = {7B6C5F8D-14B3-443D-B044-0E209AE12BDF} {1E7F7253-A6AF-43C4-A955-37BEDDA01AC0} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB8} {08DD4305-7787-4823-A53F-4D0F725A07F3} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AC0} {674B69B8-0780-4D54-AE2B-C15821FA51CB} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AC0} @@ -575,6 +519,10 @@ Global {86C54B27-717F-478C-AC8C-01F1C68A56C5} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} {C6E8D1E1-FAAC-4E02-B6A1-6164EC5E704E} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} {E899B03C-6E8E-4375-AB65-FC925D721D8B} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AB9} + {65542DEE-D3BE-4810-B85A-08E970413A21} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} + {3321CE72-26ED-4D1E-A8F5-6901FB783007} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AC0} + {791B408C-0ABC-465B-9EB1-A2422D67F418} = {1E7F7253-A6AF-43C4-A955-37BEDDA01AC0} + {B467682B-9F0E-42D8-8A20-1DE78F798793} = {8A7DA2E7-262B-4581-807A-1C45CE79CDFD} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {556014CF-5B35-4CE5-B3EF-6AB0007001AC} diff --git a/Key.snk b/Key.snk new file mode 100644 index 0000000000..d24a9ef761 Binary files /dev/null and b/Key.snk differ diff --git a/README.md b/README.md index 94875a087b..3354b68c80 100644 --- a/README.md +++ b/README.md @@ -1,95 +1,64 @@ # GitHub Extension for Visual Studio -## Notices - -### If you are having issues with the installer, please read - -If you need to upgrade, downgrade, or uninstall the extension, and are having problems doing so, refer to this issue: https://github.com/github/VisualStudio/issues/1394 which details common problems and solutions when using the installer. - -### The location of the submodules has changed as of 31-01-2017 - -If you have an existing clone, make sure to run `git submodule sync` to update your local clone with the new locations for the submodules. - ## About -The GitHub Extension for Visual Studio provides GitHub integration in Visual Studio 2015. +The GitHub Extension for Visual Studio provides GitHub integration in Visual Studio 2015 and newer. Most of the extension UI lives in the Team Explorer pane, which is available from the View menu. -Official builds of this extension are available at [the official website](https://visualstudio.github.com). +Official builds of this extension are available at the [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=GitHub.GitHubExtensionforVisualStudio). -[![Build status](https://ci.appveyor.com/api/projects/status/dl8is5iqwt9qf3t7/branch/master?svg=true)](https://ci.appveyor.com/project/github-windows/visualstudio/branch/master) -[![Crowdin](https://d322cqt584bo4o.cloudfront.net/github-visual-studio/localized.svg)](https://crowdin.com/project/github-visual-studio) -[![codecov](https://codecov.io/gh/GitHub/VisualStudio/branch/master/graph/badge.svg)](https://codecov.io/gh/GitHub/VisualStudio) +![CI](https://github.com/github/visualstudio/workflows/CI/badge.svg) -[![Join the chat at freenode:github-vs](https://img.shields.io/badge/irc-freenode:%20%23github--vs-blue.svg)](http://webchat.freenode.net/?channels=%23github-vs) [![Join the chat at https://gitter.im/github/VisualStudio](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/github/VisualStudio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Follow GitHub for Visual Studio](https://img.shields.io/twitter/follow/GitHubVS.svg?style=social "Follow GitHubVS")](https://twitter.com/githubvs?ref_src=twsrc%5Etfw) [![Join the chat at https://gitter.im/github/VisualStudio](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/github/VisualStudio?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation Visit the [documentation](https://github.com/github/VisualStudio/tree/master/docs) for details on how to use the features in the GitHub Extension for Visual Studio. -## Installing beta versions - -Older and pre-release/beta/untested versions are available at [the releases page](https://github.com/github/VisualStudio/releases), and also via a custom gallery feed for Visual Studio. +## Build requirements -You can configure the gallery by going to `Tools / Options / Extensions and Updates` and adding a new gallery with the url https://visualstudio.github.com/releases/feed.rss. The gallery will now be available from `Tools / Extensions and Updates`. +* Visual Studio 2019 + * `.NET desktop development` workload + * `.NET Core cross platform development` workload + * `Visual Studio extension development` workload -Beta releases will have `(beta)` in their title in the gallery, following the version number. You can view the release notes in the gallery by hovering over the description, or by clicking the `Release Notes` link on the right side. +The built VSIX will work with Visual Studio 2015 or newer -## Build requirements +## Build -* Visual Studio 2017 (15.7.4)+ -* Visual Studio SDK +Clone the repository and its submodules. -## Build +To be able to use the GitHub API, you'll need to: -Clone the repository and its submodules in a git GUI client or via the command line: +- [Register a new developer application](https://github.com/settings/developers) in your profile +- Create an environment variable `GitHubVS_ClientID` with your `Client ID` +- Create an environment variable `GitHubVS_ClientSecret` with your `Client Secret` -```txt -git clone https://github.com/github/VisualStudio -cd VisualStudio -git submodule init -git submodule deinit script -git submodule update -``` +Execute `build.cmd` -Open the `GitHubVS.sln` solution with Visual Studio 2017+. -To be able to use the GitHub API, you'll need to: +## Visual Studio Build -- [Register a new developer application](https://github.com/settings/developers) in your profile. -- Open [src/GitHub.Api/ApiClientConfiguration_User.cs](src/GitHub.Api/ApiClientConfiguration_User.cs) and fill out the clientId/clientSecret fields for your application. **Note this has recently changed location, so you may need to re-do this** +Build `GitHubVS.sln` using Visual Studio 2019. -Build using Visual Studio 2017 or: +## Logs +Logs can be viewed at the following location: -```txt -build.cmd -``` +`%LOCALAPPDATA%\GitHubVisualStudio\extension.log` -Install in live (non-Experimental) instances of Visual Studio 2015 and 2017: +## Troubleshooting -```txt -install.cmd -``` +If you have issues building with failures similar to: -Note, the script will only install in one instance of Visual Studio 2017 (Enterprise, Professional or Community). +> "The type or namespace name does not exist..." -## Build Flavors +or -The following can be executed via `cmd.exe`. +> "Unable to find project... Check that the project reference is valid and that the project file exists."* -To build and install a `Debug` configuration VSIX: -```txt -build.cmd Debug -install.cmd Debug -``` +Close Visual Studio and run the following command to update submodules and clean your environment. -To build and install a `Release` configuration VSIX: ```txt -build.cmd Release -install.cmd Release +clean.cmd ``` -## Logs -Logs can be viewed at the following location: - -`%LOCALAPPDATA%\GitHubVisualStudio\extension.log` ## More information - Andreia Gaita's [presentation](https://www.youtube.com/watch?v=hz2hCO8e_8w) at Codemania 2016 about this extension. @@ -100,7 +69,6 @@ Visit the [Contributor Guidelines](CONTRIBUTING.md) for details on how to contri ## Copyright -Copyright 2015 - 2018 GitHub, Inc. +Copyright 2015 - 2019 GitHub, Inc. Licensed under the [MIT License](LICENSE.md) - diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 2df8aa2316..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,36 +0,0 @@ -os: Visual Studio 2017 -version: '2.6.0.{build}' -skip_tags: true -install: -- ps: | - $full_build = Test-Path env:GHFVS_KEY - git submodule init - git submodule sync - - if ($full_build) { - $fileContent = "-----BEGIN RSA PRIVATE KEY-----`n" - $fileContent += $env:GHFVS_KEY.Replace(' ', "`n") - $fileContent += "`n-----END RSA PRIVATE KEY-----`n" - Set-Content c:\users\appveyor\.ssh\id_rsa $fileContent - } else { - git submodule deinit script - } - - git submodule update --recursive --force - nuget restore GitHubVS.sln -- choco install --no-progress BCC-MSBuildLog -- choco install --no-progress BCC-Submission -build_script: -- ps: scripts\build.ps1 -AppVeyor -BuildNumber:$env:APPVEYOR_BUILD_NUMBER -test: - categories: - except: - - Timings -on_success: -- ps: | - if ($full_build) { - script\Sign-Package -AppVeyor - } -on_finish: -- IF NOT "%BCC_TOKEN%x"=="x" BCCMSBuildLog --cloneRoot "%APPVEYOR_BUILD_FOLDER%" --input output.binlog --output checkrun.json --ownerRepo %APPVEYOR_REPO_NAME% --hash %APPVEYOR_REPO_COMMIT% -- IF NOT "%BCC_TOKEN%x"=="x" BCCSubmission -h %APPVEYOR_REPO_COMMIT% -i checkrun.json -t %BCC_TOKEN% diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000000..a3fd3771a2 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,123 @@ +# .NET Desktop +# Build and run tests for .NET Desktop or Windows classic desktop solutions. +# Add steps that publish symbols, save build artifacts, and more: +# https://docs.microsoft.com/azure/devops/pipelines/apps/windows/dot-net + +pool: + vmImage: 'windows-latest' + +variables: +- group: GitHubVS +- name: githubvsSolution + value: 'GitHubVS.sln' +- name: essentialsSolution + value: 'src\GitHub.VisualStudio.16.sln' +- name: buildPlatform + value: 'Any CPU' +- name: buildConfiguration + value: 'Release' + +jobs: +- job: build_and_test + displayName: 'Build/test GitHub for VS and Essentials' + + strategy: + maxParallel: 2 + matrix: + VSMarketplace: + ArtifactDirectory: '$(Build.ArtifactStagingDirectory)' + IsProductComponent: false + VSInstaller: + ArtifactDirectory: '$(Build.ArtifactStagingDirectory)\VSInstaller' + IsProductComponent: true + + steps: + - checkout: self + submodules: true + + # Can't use the NuGet tasks because of https://github.com/Microsoft/azure-pipelines-tasks/issues/6790 + #- task: NuGetToolInstaller@0 + #- task: NuGetCommand@2 + # inputs: + # restoreSolution: '$(solution)' + # feedsToUse: 'config' + # nugetConfigPath: nuget.config + + # Instead run nuget manually. + + - task: DotNetCoreCLI@2 + inputs: + command: custom + custom: tool + arguments: install --tool-path . nbgv + displayName: Install NBGV tool + + - script: nbgv cloud + displayName: Set the cloud build number + + - script: tools\nuget\nuget.exe restore $(githubvsSolution) + displayName: NuGet restore $(githubvsSolution) + + - script: tools\nuget\nuget.exe restore $(essentialsSolution) + displayName: NuGet restore $(essentialsSolution) + + - pwsh: mkdir $(ArtifactDirectory) -Force + displayName: Create VSIX staging directory + + - task: MSBuild@1 + displayName: GitHub for Visual Studio + inputs: + solution: $(githubvsSolution) + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + msbuildVersion: '16.0' + msbuildArguments: > + /p:TargetVsixContainer=$(ArtifactDirectory)\GitHub.VisualStudio.vsix + /p:IsProductComponent=$(IsProductComponent) + /p:DeployExtension=False + /p:GitHubVS_ClientId=$(GitHubVS_ClientId) + /p:GitHubVS_ClientSecret=$(GitHubVS_ClientSecret) + + - task: MSBuild@1 + displayName: GitHub Essentials + inputs: + solution: $(essentialsSolution) + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + msbuildVersion: '16.0' + msbuildArguments: > + /p:TargetVsixContainer=$(ArtifactDirectory)\GitHub.VisualStudio.16.vsix + /p:IsProductComponent=$(IsProductComponent) + /p:DeployExtension=False + + - task: DownloadSecureFile@1 + name: vsixsigntool_exe + inputs: + secureFile: vsixsigntool.exe + + - task: DownloadSecureFile@1 + name: certificate_pfx + inputs: + secureFile: certificate.pfx + + - script: $(vsixsigntool_exe.secureFilePath) sign /f $(certificate_pfx.secureFilePath) /p "$(certificate_password)" /sha1 9c5a6d389e1454f2ed9ee9419cdf743689709f9c /fd sha256 /tr http://timestamp.digicert.com /td sha256 $(ArtifactDirectory)\GitHub.VisualStudio.vsix + condition: not(eq(variables['Build.Reason'], 'PullRequest')) + displayName: Sign the GitHub for Visual Studio VSIX + + - script: $(vsixsigntool_exe.secureFilePath) sign /f $(certificate_pfx.secureFilePath) /p "$(certificate_password)" /sha1 9c5a6d389e1454f2ed9ee9419cdf743689709f9c /fd sha256 /tr http://timestamp.digicert.com /td sha256 $(ArtifactDirectory)\GitHub.VisualStudio.16.vsix + condition: not(eq(variables['Build.Reason'], 'PullRequest')) + displayName: Sign the GitHub Essentials VSIX + + - task: PublishBuildArtifacts@1 + inputs: + pathToPublish: $(Build.ArtifactStagingDirectory) + artifactName: 'vsix' + + - task: VSTest@2 + inputs: + searchFolder: '$(Build.SourcesDirectory)\test' + testAssemblyVer2: '**\bin\**\*Tests.dll' + platform: '$(buildPlatform)' + configuration: '$(buildConfiguration)' + diagnosticsEnabled: true + runSettingsFile: '$(Build.SourcesDirectory)\test\test.runsettings' diff --git a/build.cmd b/build.cmd index ed204a53a7..c0b247efc5 100644 --- a/build.cmd +++ b/build.cmd @@ -1,2 +1,11 @@ -@if "%1" == "" echo Please specify Debug or Release && EXIT /B -powershell -ExecutionPolicy Unrestricted scripts\build.ps1 -Package:$true -Config:%1 +@echo off + +call vars.cmd + +rem Build GitHub for Visual Studio +NuGet restore .\GitHubVS.sln +msbuild .\GitHubVS.sln /p:DeployExtension=False + +rem Build GitHub Essentials +NuGet restore .\src\GitHub.VisualStudio.16.sln +msbuild .\src\GitHub.VisualStudio.16.sln /p:DeployExtension=False diff --git a/certificate.pfx b/certificate.pfx new file mode 100644 index 0000000000..e236e59130 Binary files /dev/null and b/certificate.pfx differ diff --git a/clean.cmd b/clean.cmd new file mode 100644 index 0000000000..58de3b189c --- /dev/null +++ b/clean.cmd @@ -0,0 +1,3 @@ +git submodule update --init +git clean -xdff +git submodule foreach git clean -xdff diff --git a/crowdin.yml b/crowdin.yml deleted file mode 100644 index 64417011e1..0000000000 --- a/crowdin.yml +++ /dev/null @@ -1,9 +0,0 @@ -preserve_hierarchy: true - -files: - - source: /src/GitHub.Resources/Resources.resx - translation: /%original_path%/Resources.%locale%.resx - - source: /src/GitHub.VisualStudio/xlf/GitHub.VisualStudio.vsct.zh-CN.xlf - translation: /%original_path%/GitHub.VisualStudio.vsct.%locale%.xlf - - source: /src/GitHub.VisualStudio/xlf/VSPackage.zh-CN.xlf - translation: /%original_path%/VSPackage.%locale%.xlf diff --git a/deploy-local.cmd b/deploy-local.cmd deleted file mode 100644 index 64f80141d9..0000000000 --- a/deploy-local.cmd +++ /dev/null @@ -1 +0,0 @@ -powershell.exe .\script\deploy.ps1 -Force -NoChat -NoPush -NoUpload \ No newline at end of file diff --git a/docs/getting-started/authenticating-to-github.md b/docs/getting-started/authenticating-to-github.md index d3f83ba438..225c3dec8c 100644 --- a/docs/getting-started/authenticating-to-github.md +++ b/docs/getting-started/authenticating-to-github.md @@ -4,7 +4,7 @@ 1. In Visual Studio, select **Team Explorer** from the **View** menu.
Team Explorer in the view menu
-1. In the Team Explorer pane, click the **Manage Connectios** toolbar icon. +1. In the Team Explorer pane, click the **Manage Connections** toolbar icon.
Manage connections toolbar icon in the Team Explorer pane
1. Click the **Connect** link in the GitHub section.
Connect to GitHub
diff --git a/docs/using/cloning-a-repository-to-visual-studio.md b/docs/using/cloning-a-repository-to-visual-studio.md index 27e5cb0534..49765274d7 100644 --- a/docs/using/cloning-a-repository-to-visual-studio.md +++ b/docs/using/cloning-a-repository-to-visual-studio.md @@ -2,7 +2,7 @@ After you provide your GitHub or GitHub Enterprise credentials to GitHub for Visual Studio, the extension automatically detects the personal, collaborator and organization repositories you have access to on your account. -## Opening the clone dialog +## Opening the clone dialog ### From **Team Explorer** @@ -17,7 +17,12 @@ Next to the account you want to clone from, click **Clone**. ### From the **Start Page** -Using Visual Studio 2017, click the `GitHub` button on the `Start Page` to open the clone dialog. +Using Visual Studio 2017, click the `GitHub` button on the `Start Page` to open the clone dialog. + + +### From the **Start Window** + +Using Visual Studio 2019, on the `Start Window` select `Clone or check out code` and then click the `GitHub` button to open the clone dialog. ### From the **File** menu @@ -26,14 +31,19 @@ Go to `File > Open > Open From GitHub...` ## Clone repositories -1. In the list of repositories, scroll until you find the repository you'd like to clone. You can also filter the repository results by using the *Filter* text box. +1. In the list of repositories, scroll until you find the repository you'd like to clone. -![List of GitHub repositories that can be cloned inside a dialog](images/clone-dialog.png) +You can also filter the repository results by using the *Filter* text box. -In addition to using the list of personal, collaborator and organization repositories, you can use the URL tab to clone a public repository by its URL or using the repository owner and name. +In addition to using the list of personal, collaborator and organization repositories, you can enter a repository URL to clone a public repository. -![List of GitHub repositories that can be cloned inside a dialog](images/clone-url-dialog.png) +![Unified clone and open dialog](images/unified-clone-dialog.png) 2. If desired, change the local path that the repository will be cloned into, or leave the default as-is. 3. Once a repository is selected and the path is set, Click **Clone**. 4. In Team Explorer, under the list of solutions, double-click on a solution to open it in Visual Studio. + +## Open repositories +For any repository that you select from the list or provide a URL for that you already have cloned locally, the **Open** button becomes enabled and a message shows that you have already cloned the repository to that location. + +![Open option enabled in clone dialog](images/open-cloned-repository.png) diff --git a/docs/using/images/clone-dialog.png b/docs/using/images/clone-dialog.png deleted file mode 100644 index 43d44666ee..0000000000 Binary files a/docs/using/images/clone-dialog.png and /dev/null differ diff --git a/docs/using/images/clone-url-dialog.png b/docs/using/images/clone-url-dialog.png deleted file mode 100644 index 99c490e978..0000000000 Binary files a/docs/using/images/clone-url-dialog.png and /dev/null differ diff --git a/docs/using/images/open-cloned-repository.png b/docs/using/images/open-cloned-repository.png new file mode 100644 index 0000000000..9aa0596e53 Binary files /dev/null and b/docs/using/images/open-cloned-repository.png differ diff --git a/docs/using/images/unified-clone-dialog.png b/docs/using/images/unified-clone-dialog.png new file mode 100644 index 0000000000..814fc5b658 Binary files /dev/null and b/docs/using/images/unified-clone-dialog.png differ diff --git a/docs/using/images/view-conversation.png b/docs/using/images/view-conversation.png new file mode 100644 index 0000000000..ea037cf511 Binary files /dev/null and b/docs/using/images/view-conversation.png differ diff --git a/docs/using/publishing-an-existing-project-to-github.md b/docs/using/publishing-an-existing-project-to-github.md index 7b296056ce..d24874b10b 100644 --- a/docs/using/publishing-an-existing-project-to-github.md +++ b/docs/using/publishing-an-existing-project-to-github.md @@ -10,5 +10,5 @@ 5. Click the **Publish to GitHub** button. ![Location of the Publish to GitHub button in the Team Explorer pane](images/publish-to-github.png) 6. Enter a name and description for the repository on GitHub. -7. Check the **Private Repository** box if you want to upload the repository as a private repository on GitHub. You must have a [Developer, Team or Business account](https://github.com/pricing) to create private repositories. +7. Check the **Private Repository** box if you want to upload the repository as a private repository on GitHub. 8. Click the **Publish** button. diff --git a/docs/using/reviewing-a-pull-request-in-visual-studio.md b/docs/using/reviewing-a-pull-request-in-visual-studio.md index 89bf3beb81..f046b15023 100644 --- a/docs/using/reviewing-a-pull-request-in-visual-studio.md +++ b/docs/using/reviewing-a-pull-request-in-visual-studio.md @@ -15,13 +15,19 @@ GitHub for Visual Studio provides facilities for reviewing a pull request direct The Pull Request Details view shows the current state of the pull request, including: - information about who created the pull request - the source and target branch -- a description of the pull request +- a description of the pull request (collapsed by default) - reviewers and the status of their review - checks (if checks have been enabled for the repository) - the files changed ![The details of a single pull request in the GitHub pane](images/pr-detail-view.png) +## Viewing conversation details + +Click the comment count link in the GitHub pane to open up the conversation view. The conversation view shows the Pull Request description, a history of commits, and comments made. + +![View the conversation for a pull request](images/view-conversation.png) + ## Checking out a pull request To check out the pull request branch, click the **Checkout [branch]** link where [branch] is the name of the branch that will be checked out. diff --git a/global.json b/global.json index e620a36770..a75963ff54 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,5 @@ { - "msbuild-sdks": { - "MSBuild.Sdk.Extras": "1.6.52" - } + "msbuild-sdks": { + "MSBuild.Sdk.Extras": "1.6.61" + } } \ No newline at end of file diff --git a/install.cmd b/install.cmd deleted file mode 100644 index 0f46241066..0000000000 --- a/install.cmd +++ /dev/null @@ -1,3 +0,0 @@ -@if "%1" == "" echo Please specify Debug or Release && EXIT /B -tools\VsixUtil\vsixutil /install "build\%1\GitHub.VisualStudio.vsix" -@echo Installed %1 build of GitHub for Visual Studio diff --git a/lib/14.0/Microsoft.VisualStudio.Shell.ViewManager.dll b/lib/14.0/Microsoft.VisualStudio.Shell.ViewManager.dll new file mode 100644 index 0000000000..85c116f284 Binary files /dev/null and b/lib/14.0/Microsoft.VisualStudio.Shell.ViewManager.dll differ diff --git a/lib/16.0/Microsoft.TeamFoundation.Client.dll b/lib/16.0/Microsoft.TeamFoundation.Client.dll index 2280436cf0..d42abde8f0 100644 Binary files a/lib/16.0/Microsoft.TeamFoundation.Client.dll and b/lib/16.0/Microsoft.TeamFoundation.Client.dll differ diff --git a/lib/16.0/Microsoft.TeamFoundation.Common.dll b/lib/16.0/Microsoft.TeamFoundation.Common.dll index 5f96474375..d537d70f35 100644 Binary files a/lib/16.0/Microsoft.TeamFoundation.Common.dll and b/lib/16.0/Microsoft.TeamFoundation.Common.dll differ diff --git a/lib/16.0/Microsoft.TeamFoundation.Controls.dll b/lib/16.0/Microsoft.TeamFoundation.Controls.dll index 598e9fe14a..bdb525c762 100644 Binary files a/lib/16.0/Microsoft.TeamFoundation.Controls.dll and b/lib/16.0/Microsoft.TeamFoundation.Controls.dll differ diff --git a/lib/16.0/Microsoft.TeamFoundation.Git.Client.dll b/lib/16.0/Microsoft.TeamFoundation.Git.Client.dll index 917f58f4ae..5911b7e179 100644 Binary files a/lib/16.0/Microsoft.TeamFoundation.Git.Client.dll and b/lib/16.0/Microsoft.TeamFoundation.Git.Client.dll differ diff --git a/lib/16.0/Microsoft.TeamFoundation.Git.Controls.dll b/lib/16.0/Microsoft.TeamFoundation.Git.Controls.dll index bda68a21df..29f24fe79e 100644 Binary files a/lib/16.0/Microsoft.TeamFoundation.Git.Controls.dll and b/lib/16.0/Microsoft.TeamFoundation.Git.Controls.dll differ diff --git a/lib/16.0/Microsoft.TeamFoundation.Git.Provider.dll b/lib/16.0/Microsoft.TeamFoundation.Git.Provider.dll index 0ccb6da2e7..7d28d587ad 100644 Binary files a/lib/16.0/Microsoft.TeamFoundation.Git.Provider.dll and b/lib/16.0/Microsoft.TeamFoundation.Git.Provider.dll differ diff --git a/lib/Octokit.GraphQL.0.1.1-beta.nupkg b/lib/Octokit.GraphQL.0.1.1-beta.nupkg deleted file mode 100644 index ac26783672..0000000000 Binary files a/lib/Octokit.GraphQL.0.1.1-beta.nupkg and /dev/null differ diff --git a/nuget.config b/nuget.config index 4eb3008283..e8c7639ce6 100644 --- a/nuget.config +++ b/nuget.config @@ -1,8 +1,7 @@ - - + diff --git a/script b/script deleted file mode 160000 index 5ed9b3d7bc..0000000000 --- a/script +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5ed9b3d7bceee50d27a4a5838d4c0265bd35cc8e diff --git a/scripts/Bump-Version.ps1 b/scripts/Bump-Version.ps1 deleted file mode 100644 index f9918f1892..0000000000 --- a/scripts/Bump-Version.ps1 +++ /dev/null @@ -1,93 +0,0 @@ -<# -.SYNOPSIS - Bumps the version number of GitHub for Visual Studio -.DESCRIPTION - By default, just bumps the last component of the version number by one. An - alternate version number can be specified on the command line. - - The new version number is committed to the local repository and pushed to - GitHub. -#> - -Param( - # It would be nice to use our Validate-Version function here, but we - # can't because this Param definition has to come before any other code in the - # file. - [ValidateScript({ ($_.Major -ge 0) -and ($_.Minor -ge 0) -and ($_.Build -ge 0) })] - [System.Version] - $NewVersion = $null - , - [switch] - $BumpMajor = $false - , - [switch] - $BumpMinor = $false - , - [switch] - $BumpPatch = $false - , - [switch] - $BumpBuild = $false - , - [int] - $BuildNumber = -1 - , - [switch] - $Commit = $false - , - [switch] - $Push = $false - , - [switch] - $Force = $false - , - [switch] - $Trace = $false -) - -Set-StrictMode -Version Latest -if ($Trace) { Set-PSDebug -Trace 1 } - -. $PSScriptRoot\modules.ps1 | out-null -. $scriptsDirectory\Modules\Versioning.ps1 | out-null -. $scriptsDirectory\Modules\Vsix.ps1 | out-null -. $scriptsDirectory\Modules\SolutionInfo.ps1 | out-null -. $scriptsDirectory\Modules\AppVeyor.ps1 | out-null -. $scriptsDirectory\Modules\DirectoryBuildProps.ps1 | out-null - -if ($NewVersion -eq $null) { - if (!$BumpMajor -and !$BumpMinor -and !$BumpPatch -and !$BumpBuild){ - Die -1 "You need to indicate which part of the version to update via -BumpMajor/-BumpMinor/-BumpPatch/-BumpBuild flags or a custom version via -NewVersion" - } -} - -if ($Push -and !$Commit) { - Die 1 "Cannot push a version bump without -Commit" -} - -if ($Commit -and !$Force){ - Require-CleanWorkTree "bump version" -} - -if (!$?) { - exit 1 -} - -if ($NewVersion -eq $null) { - $currentVersion = Read-Version - $NewVersion = Generate-Version $currentVersion $BumpMajor $BumpMinor $BumpPatch $BumpBuild $BuildNumber -} - -Write-Output "Setting version to $NewVersion" -Write-Version $NewVersion - -if ($Commit) { - Write-Output "Committing version change" - Commit-Version $NewVersion - - if ($Push) { - Write-Output "Pushing version change" - $branch = & $git rev-parse --abbrev-ref HEAD - Push-Changes $branch - } -} diff --git a/scripts/Get-CheckedOutBranch.ps1 b/scripts/Get-CheckedOutBranch.ps1 deleted file mode 100644 index 38a961c2e3..0000000000 --- a/scripts/Get-CheckedOutBranch.ps1 +++ /dev/null @@ -1,31 +0,0 @@ -<# -.SYNOPSIS - Returns the name of the working directory's currently checked-out branch -#> - -Set-PSDebug -Strict - -$scriptsDirectory = Split-Path $MyInvocation.MyCommand.Path -$rootDirectory = Split-Path $scriptsDirectory - -. $scriptsDirectory\common.ps1 - -function Die([string]$message, [object[]]$output) { - if ($output) { - Write-Output $output - $message += ". See output above." - } - Write-Error $message - exit 1 -} - -$output = & $git symbolic-ref HEAD 2>&1 | %{ "$_" } -if (!$? -or ($LastExitCode -ne 0)) { - Die "Failed to determine current branch" $output -} - -if (!($output -match "^refs/heads/(\S+)$")) { - Die "Failed to determine current branch. HEAD is $output" $output -} - -$matches[1] diff --git a/scripts/Require-CleanWorkTree.ps1 b/scripts/Require-CleanWorkTree.ps1 deleted file mode 100644 index 741a05ab26..0000000000 --- a/scripts/Require-CleanWorkTree.ps1 +++ /dev/null @@ -1,57 +0,0 @@ -<# -.SYNOPSIS - Ensures the working tree has no uncommitted changes -.PARAMETER Action - The action that requires a clean work tree. This will appear in error messages. -.PARAMETER WarnOnly - When true, warns rather than dies when uncommitted changes are found. -#> - -[CmdletBinding()] -Param( - [ValidateNotNullOrEmpty()] - [string] - $Action - , - [switch] - $WarnOnly = $false -) - -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" - -. $PSScriptRoot\modules.ps1 | out-null - -# Based on git-sh-setup.sh:require_clean_work_tree in git.git, but changed not -# to ignore submodules. - -Push-Location $rootDirectory - -Run-Command -Fatal { & $git rev-parse --verify HEAD | Out-Null } - -& $git update-index -q --refresh - -& $git diff-files --quiet -$error = "" -if ($LastExitCode -ne 0) { - $error = "You have unstaged changes." -} - -& $git diff-index --cached --quiet HEAD -- -if ($LastExitCode -ne 0) { - if ($error) { - $error += " Additionally, your index contains uncommitted changes." - } else { - $error = "Your index contains uncommitted changes." - } -} - -if ($error) { - if ($WarnOnly) { - Write-Warning "$error Continuing anyway." - } else { - Die 2 ("Cannot $Action" + ": $error") - } -} - -Pop-Location diff --git a/scripts/Run-CodeCoverage.ps1 b/scripts/Run-CodeCoverage.ps1 deleted file mode 100644 index 752dad5ff8..0000000000 --- a/scripts/Run-CodeCoverage.ps1 +++ /dev/null @@ -1,83 +0,0 @@ -<# -.SYNOPSIS - Runs NUnit -#> - -[CmdletBinding()] -Param( - [string] - $Configuration - , - [switch] - $AppVeyor = $false -) - -$scriptsDirectory = $PSScriptRoot -$rootDirectory = Split-Path ($scriptsDirectory) -. $scriptsDirectory\modules.ps1 | out-null - -$nunitDirectory = Join-Path $rootDirectory packages\NUnit.ConsoleRunner.3.7.0\tools -$nunitConsoleRunner = Join-Path $nunitDirectory nunit3-console.exe - -$testAssemblies = @( - "test\GitHub.Api.UnitTests\bin\$Configuration\net461\GitHub.Api.UnitTests.dll", - "test\GitHub.App.UnitTests\bin\$Configuration\net461\GitHub.App.UnitTests.dll", - "test\GitHub.Exports.Reactive.UnitTests\bin\$Configuration\net461\GitHub.Exports.Reactive.UnitTests.dll", - "test\GitHub.Exports.UnitTests\bin\$Configuration\net461\GitHub.Exports.UnitTests.dll", - "test\GitHub.Extensions.UnitTests\bin\$Configuration\net461\GitHub.Extensions.UnitTests.dll", - "test\GitHub.InlineReviews.UnitTests\bin\$Configuration\net461\GitHub.InlineReviews.UnitTests.dll", - "test\GitHub.TeamFoundation.UnitTests\bin\$Configuration\net461\GitHub.TeamFoundation.UnitTests.dll", - "test\GitHub.UI.UnitTests\bin\$Configuration\net461\GitHub.UI.UnitTests.dll", - "test\GitHub.VisualStudio.UnitTests\bin\$Configuration\net461\GitHub.VisualStudio.UnitTests.dll", - "test\MetricsTests\MetricsTests\bin\$Configuration\MetricsTests.dll", - "test\TrackingCollectionTests\bin\$Configuration\net461\TrackingCollectionTests.dll" -) - -$opencoverTargetArgs = ($testAssemblies -join " ") + " --where \`"cat!=Timings and cat!=CodeCoverageFlake\`" --inprocess --noresult" - -$opencoverDirectory = Join-Path $env:USERPROFILE .nuget\packages\opencover\4.6.519\tools -$opencover = Join-Path $opencoverDirectory OpenCover.Console.exe -$opencoverArgs = @( - "-target:`"$nunitConsoleRunner`"", - "-targetargs:`"$opencoverTargetArgs`"", - "-filter:`"+[GitHub*]* -[GitHub*Unit]GitHub.*.SampleData -[GitHub*UnitTests]*`"", - "-excludebyfile:*.xaml;*.xaml.cs", - "-register:user -output:$rootDirectory\coverage.xml" -) -join " " - -$codecovDirectory = Join-Path $env:USERPROFILE .nuget\packages\codecov\1.1.0\tools -$codecov = Join-Path $codecovDirectory codecov.exe -$codecovArgs = "-f $rootDirectory\coverage.xml" - -& { - Trap { - Write-Output "OpenCover trapped" - exit 0 - } - - Write-Output $opencover - - Run-Process 600 $opencover $opencoverArgs - - if (!$?) { - Write-Output "OpenCover failed" - exit 0 - } -} - -if($AppVeyor) { - & { - Trap { - Write-Output "Codecov trapped" - exit 0 - } - - Push-AppveyorArtifact "$rootDirectory\coverage.xml" - Run-Process 300 $codecov $codecovArgs - - if (!$?) { - Write-Output "Codecov failed" - exit 0 - } - } -} diff --git a/scripts/Run-NUnit.ps1 b/scripts/Run-NUnit.ps1 deleted file mode 100644 index ac4662198a..0000000000 --- a/scripts/Run-NUnit.ps1 +++ /dev/null @@ -1,54 +0,0 @@ -<# -.SYNOPSIS - Runs NUnit -#> - -[CmdletBinding()] -Param( - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [string] - $BasePathToProject - , - [Parameter(Mandatory=$true)] - [ValidateNotNullOrEmpty()] - [string] - $Project - , - [int] - $TimeoutDuration - , - [string] - $Configuration - , - [switch] - $AppVeyor = $false -) - -$scriptsDirectory = $PSScriptRoot -$rootDirectory = Split-Path ($scriptsDirectory) -. $scriptsDirectory\modules.ps1 | out-null - -$dll = "$BasePathToProject\$Project\bin\$Configuration\$Project.dll" -$nunitDirectory = Join-Path $rootDirectory packages\NUnit.ConsoleRunner.3.7.0\tools -$consoleRunner = Join-Path $nunitDirectory nunit3-console.exe -$xml = Join-Path $rootDirectory "nunit-$Project.xml" - -& { - Trap { - Write-Output "$Project tests failed" - exit -1 - } - - $args = @() - if ($AppVeyor) { - $args = $dll, "--where", "cat!=Timings", "--result=$xml;format=AppVeyor" - } else { - $args = $dll, "--where", "cat!=Timings", "--result=$xml" - } - - Run-Process -Fatal $TimeoutDuration $consoleRunner $args - if (!$?) { - Die 1 "$Project tests failed" - } -} diff --git a/scripts/build.ps1 b/scripts/build.ps1 deleted file mode 100644 index aa44a88934..0000000000 --- a/scripts/build.ps1 +++ /dev/null @@ -1,84 +0,0 @@ -<# -.SYNOPSIS - Builds and (optionally) runs tests for GitHub for Visual Studio -.DESCRIPTION - Build GHfVS -.PARAMETER Clean - When true, all untracked (and ignored) files will be removed from the work - tree and all submodules. Defaults to false. -.PARAMETER Config - Debug or Release -.PARAMETER RunTests - Runs the tests (defauls to false) -#> -[CmdletBinding()] - -Param( - [switch] - $UpdateSubmodules = $false - , - [switch] - $Clean = $false - , - [ValidateSet('Debug', 'Release')] - [string] - $Config = "Release" - , - [switch] - $Package = $false - , - [switch] - $AppVeyor = $false - , - [switch] - $BumpVersion = $false - , - [int] - $BuildNumber = -1 - , - [switch] - $Trace = $false -) - -Set-StrictMode -Version Latest -if ($Trace) { - Set-PSDebug -Trace 1 -} - -. $PSScriptRoot\modules.ps1 | out-null -$env:PATH = "$scriptsDirectory;$scriptsDirectory\Modules;$env:PATH" - -Import-Module $scriptsDirectory\Modules\Debugging.psm1 -Vsix | out-null - -Push-Location $rootDirectory - -if ($UpdateSubmodules) { - Update-Submodules -} - -if ($Clean) { - Clean-WorkingTree -} - -$fullBuild = Test-Path env:GHFVS_KEY -$publishable = $fullBuild -and $AppVeyor -and ($env:APPVEYOR_PULL_REQUEST_NUMBER -or $env:APPVEYOR_REPO_BRANCH -eq "master") -if ($publishable) { #forcing a deploy flag for CI - $Package = $true - $BumpVersion = $true -} - -if ($BumpVersion) { - Write-Output "Bumping the version" - Bump-Version -BumpBuild -BuildNumber:$BuildNumber -} - -if ($Package) { - Write-Output "Building and packaging GitHub for Visual Studio" -} else { - Write-Output "Building GitHub for Visual Studio" -} - -Build-Solution GitHubVs.sln "Build" $config -Deploy:$Package - -Pop-Location diff --git a/scripts/clearerror.cmd b/scripts/clearerror.cmd deleted file mode 100644 index 9a18480a67..0000000000 --- a/scripts/clearerror.cmd +++ /dev/null @@ -1 +0,0 @@ -@echo off \ No newline at end of file diff --git a/scripts/common.ps1 b/scripts/common.ps1 deleted file mode 100644 index 3637124792..0000000000 --- a/scripts/common.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -$scriptsDirectory = Split-Path $MyInvocation.MyCommand.Path -$rootDirectory = Split-Path ($scriptsDirectory) - -function Die([string]$message, [object[]]$output) { - if ($output) { - Write-Output $output - $message += ". See output above." - } - Throw (New-Object -TypeName ScriptException -ArgumentList $message) -} - -if (Test-Path "C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe") { - $msbuild = "C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe" -} -elseif (Test-Path "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe") { - $msbuild = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe" -} -else { - Die("No suitable msbuild.exe found.") -} - -$git = (Get-Command 'git.exe').Path -if (!$git) { - $git = Join-Path $rootDirectory 'PortableGit\cmd\git.exe' -} -if (!$git) { - throw "Couldn't find installed an git.exe" -} - -$nuget = Join-Path $rootDirectory "tools\nuget\nuget.exe" - -function Create-TempDirectory { - $path = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) - New-Item -Type Directory $path -} - -function Build-Solution([string]$solution,[string]$target,[string]$configuration, [bool]$ForVSInstaller) { - Run-Command -Fatal { & $nuget restore $solution -NonInteractive -Verbosity detailed } - $flag1 = "" - $flag2 = "" - if ($ForVSInstaller) { - $flag1 = "/p:IsProductComponent=true" - $flag2 = "/p:TargetVsixContainer=$rootDirectory\build\vsinstaller\GitHub.VisualStudio.vsix" - new-item -Path $rootDirectory\build\vsinstaller -ItemType Directory -Force | Out-Null - } - - Write-Output "$msbuild $solution /target:$target /property:Configuration=$configuration /p:DeployExtension=false /verbosity:minimal /p:VisualStudioVersion=14.0 $flag1 $flag2" - Run-Command -Fatal { & $msbuild $solution /target:$target /property:Configuration=$configuration /p:DeployExtension=false /verbosity:minimal /p:VisualStudioVersion=14.0 $flag1 $flag2 } -} - -function Push-Changes([string]$branch) { - Push-Location $rootDirectory - - Write-Output "Pushing $Branch to GitHub..." - - Run-Command -Fatal { & $git push origin $branch } - - Pop-Location -} - -Add-Type -AssemblyName "System.Core" -Add-Type -TypeDefinition @" -public class ScriptException : System.Exception -{ - public ScriptException(string message) : base(message) - { - } -} -"@ diff --git a/scripts/modules.ps1 b/scripts/modules.ps1 deleted file mode 100644 index 83fe9e39f7..0000000000 --- a/scripts/modules.ps1 +++ /dev/null @@ -1,202 +0,0 @@ -Add-Type -AssemblyName "System.Core" -Add-Type -TypeDefinition @" -public class ScriptException : System.Exception -{ - public int ExitCode { get; private set; } - public ScriptException(string message, int exitCode) : base(message) - { - this.ExitCode = exitCode; - } -} -"@ - -New-Module -ScriptBlock { - $rootDirectory = Split-Path ($PSScriptRoot) - $scriptsDirectory = Join-Path $rootDirectory "scripts" - $nuget = Join-Path $rootDirectory "tools\nuget\nuget.exe" - Export-ModuleMember -Variable scriptsDirectory,rootDirectory,nuget -} - -New-Module -ScriptBlock { - function Die([int]$exitCode, [string]$message, [object[]]$output) { - #$host.SetShouldExit($exitCode) - if ($output) { - Write-Host $output - $message += ". See output above." - } - $hash = @{ - Message = $message - ExitCode = $exitCode - Output = $output - } - Throw (New-Object -TypeName ScriptException -ArgumentList $message,$exitCode) - #throw $message - } - - - function Run-Command([scriptblock]$Command, [switch]$Fatal, [switch]$Quiet) { - $output = "" - - $exitCode = 0 - - if ($Quiet) { - $output = & $command 2>&1 | %{ "$_" } - } else { - & $command - } - - if (!$? -and $LastExitCode -ne 0) { - $exitCode = $LastExitCode - } elseif ($? -and $LastExitCode -ne 0) { - $exitCode = $LastExitCode - } - - if ($exitCode -ne 0) { - if (!$Fatal) { - Write-Host "``$Command`` failed" $output - } else { - Die $exitCode "``$Command`` failed" $output - } - } - $output - } - - function Run-Process([int]$Timeout, [string]$Command, [string[]]$Arguments, [switch]$Fatal = $false) - { - $args = ($Arguments | %{ "`"$_`"" }) - [object[]] $output = "$Command " + $args - $exitCode = 0 - $outputPath = [System.IO.Path]::GetTempFileName() - $process = Start-Process -PassThru -NoNewWindow -RedirectStandardOutput $outputPath $Command ($args | %{ "`"$_`"" }) - Wait-Process -InputObject $process -Timeout $Timeout -ErrorAction SilentlyContinue - if ($process.HasExited) { - $output += Get-Content $outputPath - $exitCode = $process.ExitCode - } else { - $output += "Process timed out. Backtrace:" - $output += Get-DotNetStack $process.Id - $exitCode = 9999 - } - Stop-Process -InputObject $process - Remove-Item $outputPath - if ($exitCode -ne 0) { - if (!$Fatal) { - Write-Host "``$Command`` failed" $output - } else { - Die $exitCode "``$Command`` failed" $output - } - } - $output - } - - function Create-TempDirectory { - $path = Join-Path ([System.IO.Path]::GetTempPath()) ([System.IO.Path]::GetRandomFileName()) - New-Item -Type Directory $path - } - - Export-ModuleMember -Function Die,Run-Command,Run-Process,Create-TempDirectory -} - -New-Module -ScriptBlock { - function Find-MSBuild() { - if (Test-Path "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\bin\MSBuild.exe") { - $msbuild = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Community\MSBuild\15.0\bin\MSBuild.exe" - } - elseif (Test-Path "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe") { - $msbuild = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Enterprise\MSBuild\15.0\Bin\MSBuild.exe" - } - elseif (Test-Path "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\MSBuild.exe") { - $msbuild = "C:\Program Files (x86)\Microsoft Visual Studio\2017\Professional\MSBuild\15.0\Bin\MSBuild.exe" - } - else { - Die("No suitable msbuild.exe found.") - } - $msbuild - } - - function Build-Solution([string]$solution, [string]$target, [string]$configuration, [switch]$ForVSInstaller, [bool]$Deploy = $false) { - Run-Command -Fatal { & $nuget restore $solution -NonInteractive -Verbosity detailed } - $flag1 = "" - $flag2 = "" - if ($ForVSInstaller) { - $flag1 = "/p:IsProductComponent=true" - $flag2 = "/p:TargetVsixContainer=$rootDirectory\build\vsinstaller\GitHub.VisualStudio.vsix" - new-item -Path $rootDirectory\build\vsinstaller -ItemType Directory -Force | Out-Null - } elseif (!$Deploy) { - $configuration += "WithoutVsix" - $flag1 = "/p:Package=Skip" - } - - $msbuild = Find-MSBuild - - Write-Host "$msbuild $solution /target:$target /property:Configuration=$configuration /p:DeployExtension=false /verbosity:minimal /p:VisualStudioVersion=15.0 /bl:output.binlog $flag1 $flag2" - Run-Command -Fatal { & $msbuild $solution /target:$target /property:Configuration=$configuration /p:DeployExtension=false /verbosity:minimal /p:VisualStudioVersion=15.0 /bl:output.binlog $flag1 $flag2 } - } - - Export-ModuleMember -Function Find-MSBuild,Build-Solution -} - -New-Module -ScriptBlock { - function Find-Git() { - $git = (Get-Command 'git.exe').Path - if (!$git) { - $git = Join-Path $rootDirectory 'PortableGit\cmd\git.exe' - } - if (!$git) { - Die("Couldn't find installed an git.exe") - } - $git - } - - function Push-Changes([string]$branch) { - Push-Location $rootDirectory - - Write-Host "Pushing $Branch to GitHub..." - - Run-Command -Fatal { & $git push origin $branch } - - Pop-Location - } - - function Update-Submodules { - Write-Host "Updating submodules..." - Write-Host "" - - Run-Command -Fatal { git submodule init } - Run-Command -Fatal { git submodule sync } - Run-Command -Fatal { git submodule update --recursive --force } - } - - function Clean-WorkingTree { - Write-Host "Cleaning work tree..." - Write-Host "" - - Run-Command -Fatal { git clean -xdf } - Run-Command -Fatal { git submodule foreach git clean -xdf } - } - - function Get-HeadSha { - Run-Command -Quiet { & $git rev-parse HEAD } - } - - $git = Find-Git - Export-ModuleMember -Function Find-Git,Push-Changes,Update-Submodules,Clean-WorkingTree,Get-HeadSha -} - -New-Module -ScriptBlock { - function Write-Manifest([string]$directory) { - Add-Type -Path (Join-Path $rootDirectory packages\Newtonsoft.Json.6.0.8\lib\net35\Newtonsoft.Json.dll) - - $manifest = @{ - NewestExtension = @{ - Version = [string](Read-CurrentVersionVsix) - Commit = [string](Get-HeadSha) - } - } - - $manifestPath = Join-Path $directory manifest - [Newtonsoft.Json.JsonConvert]::SerializeObject($manifest) | Out-File $manifestPath -Encoding UTF8 - } - - Export-ModuleMember -Function Write-Manifest -} \ No newline at end of file diff --git a/scripts/modules/AppVeyor.ps1 b/scripts/modules/AppVeyor.ps1 deleted file mode 100644 index 49470283d0..0000000000 --- a/scripts/modules/AppVeyor.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -Set-StrictMode -Version Latest - -New-Module -ScriptBlock { - - function Get-AppVeyorPath { - Join-Path $rootDirectory appveyor.yml - } - - function Read-VersionAppVeyor { - $file = Get-AppVeyorPath - $currentVersion = Get-Content $file | %{ - $regex = "`^version: '(\d+\.\d+\.\d+)\.`{build`}'`$" - if ($_ -match $regex) { - $matches[1] - } - } - [System.Version] $currentVersion - } - - function Write-VersionAppVeyor([System.Version]$version) { - $file = Get-AppVeyorPath - $numberOfReplacements = 0 - $newContent = Get-Content $file | %{ - $newString = $_ - $regex = "version: '(\d+\.\d+\.\d+)" - if ($newString -match $regex) { - $numberOfReplacements++ - $newString = $newString -replace $regex, "version: '$($version.Major).$($version.Minor).$($version.Build)" - } - $newString - } - - if ($numberOfReplacements -ne 1) { - Die 1 "Expected to replace the version number in 1 place in appveyor.yml (version) but actually replaced it in $numberOfReplacements" - } - - $newContent | Set-Content $file - } - - Export-ModuleMember -Function Get-AppVeyorPath,Read-VersionAppVeyor,Write-VersionAppVeyor -} \ No newline at end of file diff --git a/scripts/modules/BuildUtils.psm1 b/scripts/modules/BuildUtils.psm1 deleted file mode 100644 index f93d6eecb2..0000000000 --- a/scripts/modules/BuildUtils.psm1 +++ /dev/null @@ -1,18 +0,0 @@ -Set-StrictMode -Version Latest - -function Update-Submodules { - Write-Output "Updating submodules..." - Write-Output "" - - Run-Command -Fatal { git submodule init } - Run-Command -Fatal { git submodule sync } - Run-Command -Fatal { git submodule update --recursive --force } -} - -function Clean-WorkingTree { - Write-Output "Cleaning work tree..." - Write-Output "" - - Run-Command -Fatal { git clean -xdf } - Run-Command -Fatal { git submodule foreach git clean -xdf } -} \ No newline at end of file diff --git a/scripts/modules/Debugging.psm1 b/scripts/modules/Debugging.psm1 deleted file mode 100644 index 2ca851ec0a..0000000000 --- a/scripts/modules/Debugging.psm1 +++ /dev/null @@ -1,26 +0,0 @@ -Set-StrictMode -Version Latest -$ErrorActionPreference = "Stop" - -$rootDirectory = Split-Path (Split-Path (Split-Path $MyInvocation.MyCommand.Path)) -$cdb = Join-Path $rootDirectory "tools\Debugging Tools for Windows\cdb.exe" - -function Get-DotNetStack([int]$ProcessId) { - $commands = @( - ".cordll -ve -u -l", - ".loadby sos clr", - "!eestack -ee", - ".detach", - "q" - ) - - $Env:_NT_SYMBOL_PATH = "cache*${Env:PROGRAMDATA}\dbg\sym;SRV*http://msdl.microsoft.com/download/symbols;srv*http://windows-symbols.githubapp.com/symbols" - $output = & $cdb -lines -p $ProcessId -c ($commands -join "; ") - if ($LastExitCode -ne 0) { - $output - throw "Error running cdb" - } - - $start = ($output | Select-String -List -Pattern "^Thread 0").LineNumber - 1 - $end = ($output | Select-String -List -Pattern "^Detached").LineNumber - 2 - $output[$start..$end] -} diff --git a/scripts/modules/DirectoryBuildProps.ps1 b/scripts/modules/DirectoryBuildProps.ps1 deleted file mode 100644 index 811ff3d19f..0000000000 --- a/scripts/modules/DirectoryBuildProps.ps1 +++ /dev/null @@ -1,24 +0,0 @@ -Set-StrictMode -Version Latest - -New-Module -ScriptBlock { - function Get-DirectoryBuildPropsPath { - Join-Path $rootDirectory Directory.Build.Props - } - - function Get-DirectoryBuildProps { - $xmlLines = Get-Content (Get-DirectoryBuildPropsPath) -encoding UTF8 - [xml] $xmlLines - } - - function Write-DirectoryBuildProps([System.Version]$version) { - - $document = Get-DirectoryBuildProps - - $numberOfReplacements = 0 - $document.Project.PropertyGroup.Version = $version.ToString() - - $document.Save((Get-DirectoryBuildPropsPath)) - } - - Export-ModuleMember -Function Write-DirectoryBuildProps -} \ No newline at end of file diff --git a/scripts/modules/SolutionInfo.ps1 b/scripts/modules/SolutionInfo.ps1 deleted file mode 100644 index 4e1d6e1d0f..0000000000 --- a/scripts/modules/SolutionInfo.ps1 +++ /dev/null @@ -1,41 +0,0 @@ -Set-StrictMode -Version Latest - -New-Module -ScriptBlock { - - function Get-SolutionInfoPath { - Join-Path $rootDirectory src\common\SolutionInfo.cs - } - - function Read-VersionSolutionInfo { - $file = Get-SolutionInfoPath - $currentVersion = Get-Content $file | %{ - $regex = "const string Version = `"(\d+\.\d+\.\d+\.\d+)`";" - if ($_ -match $regex) { - $matches[1] - } - } - [System.Version] $currentVersion - } - - function Write-VersionSolutionInfo([System.Version]$version) { - $file = Get-SolutionInfoPath - $numberOfReplacements = 0 - $newContent = Get-Content $file | %{ - $newString = $_ - $regex = "(string Version = `")\d+\.\d+\.\d+\.\d+" - if ($_ -match $regex) { - $numberOfReplacements++ - $newString = $newString -replace $regex, "string Version = `"$version" - } - $newString - } - - if ($numberOfReplacements -ne 1) { - Die 1 "Expected to replace the version number in 1 place in SolutionInfo.cs (Version) but actually replaced it in $numberOfReplacements" - } - - $newContent | Set-Content $file - } - - Export-ModuleMember -Function Get-SolutionInfoPath,Read-VersionSolutionInfo,Write-VersionSolutionInfo -} \ No newline at end of file diff --git a/scripts/modules/Versioning.ps1 b/scripts/modules/Versioning.ps1 deleted file mode 100644 index e7f2efad7a..0000000000 --- a/scripts/modules/Versioning.ps1 +++ /dev/null @@ -1,69 +0,0 @@ -Set-StrictMode -Version Latest - -New-Module -ScriptBlock { - - function Validate-Version([System.Version]$version) { - ($version.Major -ge 0) -and ($version.Minor -ge 0) -and ($version.Build -ge 0) - } - - function Generate-Version([System.Version]$currentVersion, - [bool]$BumpMajor, [bool] $BumpMinor, - [bool]$BumpPatch, [bool] $BumpBuild, - [int]$BuildNumber = -1) { - - if (!(Validate-Version $currentVersion)) { - Die 1 "Invalid current version $currentVersion" - } - - if ($BumpMajor) { - New-Object -TypeName System.Version -ArgumentList ($currentVersion.Major + 1), $currentVersion.Minor, $currentVersion.Build, 0 - } elseif ($BumpMinor) { - New-Object -TypeName System.Version -ArgumentList $currentVersion.Major, ($currentVersion.Minor + 1), $currentVersion.Build, 0 - } elseif ($BumpPatch) { - New-Object -TypeName System.Version -ArgumentList $currentVersion.Major, $currentVersion.Minor, ($currentVersion.Build + 1), 0 - } elseif ($BumpBuild) { - if ($BuildNumber -ge 0) { - [System.Version] "$($currentVersion.Major).$($currentVersion.Minor).$($currentVersion.Build).$BuildNumber" - } else { - $timestamp = [System.DateTimeOffset]::UtcNow.ToUnixTimeSeconds() - [System.Version] "$($currentVersion.Major).$($currentVersion.Minor).$($currentVersion.Build).$timestamp" - } - } - else { - $currentVersion - } - } - - function Read-Version { - Read-VersionAppVeyor - } - - function Write-Version([System.Version]$version) { - Write-VersionVsixManifest $version - Write-VersionSolutionInfo $version - Write-VersionAppVeyor $version - Write-DirectoryBuildProps $version - Push-Location $rootDirectory - New-Item -Type Directory -ErrorAction SilentlyContinue build | out-null - Set-Content build\version $version - Pop-Location - } - - function Commit-Version([System.Version]$version) { - - Write-Host "Committing version bump..." - - Push-Location $rootDirectory - - Run-Command -Fatal { & $git commit --message "Bump version to $version" -- } - - $output = Start-Process $git "commit --all --message ""Bump version to $version""" -wait -NoNewWindow -ErrorAction Continue -PassThru - if ($output.ExitCode -ne 0) { - Die 1 "Error committing version bump" - } - - Pop-Location - } - - Export-ModuleMember -Function Validate-Version,Write-Version,Commit-Version,Generate-Version,Read-Version -} diff --git a/scripts/modules/Vsix.ps1 b/scripts/modules/Vsix.ps1 deleted file mode 100644 index 63563d3f00..0000000000 --- a/scripts/modules/Vsix.ps1 +++ /dev/null @@ -1,35 +0,0 @@ -Set-StrictMode -Version Latest - -New-Module -ScriptBlock { - $gitHubDirectory = Join-Path $rootDirectory src\GitHub.VisualStudio - - function Get-VsixManifestPath { - Join-Path $gitHubDirectory source.extension.vsixmanifest - } - - function Get-VsixManifestXml { - $xmlLines = Get-Content (Get-VsixManifestPath) - # If we don't explicitly join the lines with CRLF, comments in the XML will - # end up with LF line-endings, which will make Git spew a warning when we - # try to commit the version bump. - $xmlText = $xmlLines -join [System.Environment]::NewLine - - [xml] $xmlText - } - - function Read-CurrentVersionVsix { - [System.Version] (Get-VsixManifestXml).PackageManifest.Metadata.Identity.Version - } - - function Write-VersionVsixManifest([System.Version]$version) { - - $document = Get-VsixManifestXml - - $numberOfReplacements = 0 - $document.PackageManifest.Metadata.Identity.Version = $version.ToString() - - $document.Save((Get-VsixManifestPath)) - } - - Export-ModuleMember -Function Read-CurrentVersionVsix,Write-VersionVsixManifest -} \ No newline at end of file diff --git a/scripts/test.ps1 b/scripts/test.ps1 deleted file mode 100644 index 6e64d9df63..0000000000 --- a/scripts/test.ps1 +++ /dev/null @@ -1,103 +0,0 @@ -<# -.SYNOPSIS - Runs tests for GitHub for Visual Studio -.DESCRIPTION - Build GHfVS -.PARAMETER Clean - When true, all untracked (and ignored) files will be removed from the work - tree and all submodules. Defaults to false. -#> -[CmdletBinding()] - -Param( - [ValidateSet('Debug', 'Release')] - [string] - $Config = "Release" - , - [int] - $TimeoutDuration = 180 - , - [switch] - $Trace = $false - -) - -Set-StrictMode -Version Latest -if ($Trace) { - Set-PSDebug -Trace 1 -} - -$env:PATH = "$PSScriptRoot;$env:PATH" - -$exitcode = 0 - -Write-Output "Running Tracking Collection Tests..." -Run-NUnit test TrackingCollectionTests $TimeoutDuration $config -if (!$?) { - $exitcode = 1 -} - -Write-Output "Running GitHub.Api.UnitTests..." -Run-NUnit test GitHub.Api.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 2 -} - -Write-Output "Running GitHub.App.UnitTests..." -Run-NUnit test GitHub.App.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 3 -} - -Write-Output "Running GitHub.Exports.Reactive.UnitTests..." -Run-NUnit test GitHub.Exports.Reactive.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 4 -} - -Write-Output "Running GitHub.Exports.UnitTests..." -Run-NUnit test GitHub.Exports.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 5 -} - -Write-Output "Running GitHub.Extensions.UnitTests..." -Run-NUnit test GitHub.Extensions.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 6 -} - -Write-Output "Running GitHub.Primitives.UnitTests..." -Run-NUnit test GitHub.Primitives.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 7 -} - -Write-Output "Running GitHub.TeamFoundation.UnitTests..." -Run-NUnit test GitHub.TeamFoundation.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 8 -} - -Write-Output "Running GitHub.UI.UnitTests..." -Run-NUnit test GitHub.UI.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 9 -} - -Write-Output "Running GitHub.VisualStudio.UnitTests..." -Run-NUnit test GitHub.VisualStudio.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 10 -} - -Write-Output "Running GitHub.InlineReviews.UnitTests..." -Run-NUnit test GitHub.InlineReviews.UnitTests $TimeoutDuration $config -if (!$?) { - $exitcode = 11 -} - -if ($exitcode -ne 0) { - $host.SetShouldExit($exitcode) -} -exit $exitcode \ No newline at end of file diff --git a/signingkey.snk b/signingkey.snk deleted file mode 100644 index 371008d5a6..0000000000 Binary files a/signingkey.snk and /dev/null differ diff --git a/src/CredentialManagement/NativeMethods.cs b/src/CredentialManagement/NativeMethods.cs index cd5dc3ef42..c88a973f60 100644 --- a/src/CredentialManagement/NativeMethods.cs +++ b/src/CredentialManagement/NativeMethods.cs @@ -4,6 +4,10 @@ using System.Text; using Microsoft.Win32.SafeHandles; +#pragma warning disable CA1034 // Nested types should not be visible +#pragma warning disable CA1051 // Do not declare visible instance fields +#pragma warning disable CA1707 // Identifiers should not contain underscores + namespace GitHub.Authentication.CredentialManagement { public static class NativeMethods diff --git a/src/GitHub.Api/ApiClientConfiguration.cs b/src/GitHub.Api/ApiClientConfiguration.cs index 937f837db7..4336ea36df 100644 --- a/src/GitHub.Api/ApiClientConfiguration.cs +++ b/src/GitHub.Api/ApiClientConfiguration.cs @@ -40,7 +40,7 @@ static ApiClientConfiguration() /// /// Gets the ideal scopes requested by the application. /// - public static IReadOnlyList RequestedScopes { get; } = new[] { "user", "repo", "gist", "write:public_key", "read:org" }; + public static IReadOnlyList RequestedScopes { get; } = new[] { "user", "repo", "gist", "write:public_key", "read:org", "workflow" }; /// /// Gets a note that will be stored with the OAUTH token. diff --git a/src/GitHub.Api/ApiClientConfiguration_User.cs b/src/GitHub.Api/ApiClientConfiguration_User.cs deleted file mode 100644 index fdffb967e8..0000000000 --- a/src/GitHub.Api/ApiClientConfiguration_User.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace GitHub.Api -{ - static partial class ApiClientConfiguration - { - const string clientId = "YOUR CLIENT ID HERE"; - const string clientSecret = "YOUR CLIENT SECRET HERE"; - - static partial void Configure() - { - ClientId = clientId; - ClientSecret = clientSecret; - } - } -} diff --git a/src/GitHub.Api/Caching/FileCache.cs b/src/GitHub.Api/Caching/FileCache.cs new file mode 100644 index 0000000000..dd5f937e61 --- /dev/null +++ b/src/GitHub.Api/Caching/FileCache.cs @@ -0,0 +1,1295 @@ +/* +Copyright 2012, 2013, 2017 Adam Carter (http://adam-carter.com) + +This file is part of FileCache (http://github.com/acarteas/FileCache). + +FileCache is distributed under the Apache License 2.0. +Consult "LICENSE.txt" included in this package for the Apache License 2.0. +*/ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Serialization; +using System.Runtime.Serialization.Formatters.Binary; +using System.Threading; +using System.Threading.Tasks; + +namespace System.Runtime.Caching +{ + public class FileCache : ObjectCache + { + private static int _nameCounter = 1; + private string _name = ""; + private SerializationBinder _binder; + private string _cacheSubFolder = "cache"; + private string _policySubFolder = "policy"; + private TimeSpan _cleanInterval = new TimeSpan(7, 0, 0, 0); // default to 1 week + private const string LastCleanedDateFile = "cache.lcd"; + private const string CacheSizeFile = "cache.size"; + // this is a file used to prevent multiple processes from trying to "clean" at the same time + private const string SemaphoreFile = "cache.sem"; + private long _currentCacheSize = 0; + private PayloadMode _readMode = PayloadMode.Serializable; + public string CacheDir { get; protected set; } + + + /// + /// Used to store the default region when accessing the cache via [] calls + /// + public string DefaultRegion { get; set; } + + /// + /// Used to set the default policy when setting cache values via [] calls + /// + public CacheItemPolicy DefaultPolicy { get; set; } + + /// + /// Specified how the cache payload is to be handled. + /// + public enum PayloadMode + { + /// + /// Treat the payload a a serializable object. + /// + Serializable, + /// + /// Treat the payload as a file name. File content will be copied on add, while get returns the file name. + /// + Filename, + /// + /// Treat the paylad as raw bytes. A byte[] and readable streams are supported on add. + /// + RawBytes + } + + /// + /// Specified whether the payload is deserialized or just the file name. + /// + public PayloadMode PayloadReadMode + { + get => _readMode; + set + { + if (value == PayloadMode.RawBytes) + { + throw new ArgumentException("The read mode cannot be set to RawBytes. Use the file name please."); + } + _readMode = value; + } + } + + /// + /// Specified how the payload is to be handled on add operations. + /// + public PayloadMode PayloadWriteMode { get; set; } = PayloadMode.Serializable; + + /// + /// The amount of time before expiry that a filename will be used as a payoad. I.e. + /// the amount of time the cache's user can safely use the file delivered as a payload. + /// Default 10 minutes. + /// + public TimeSpan FilenameAsPayloadSafetyMargin = TimeSpan.FromMinutes(10); + + /// + /// Used to determine how long the FileCache will wait for a file to become + /// available. Default (00:00:00) is indefinite. Should the timeout be + /// reached, an exception will be thrown. + /// + public TimeSpan AccessTimeout { get; set; } + + /// + /// Used to specify the disk size, in bytes, that can be used by the File Cache + /// + public long MaxCacheSize { get; set; } + + /// + /// Returns the approximate size of the file cache + /// + public long CurrentCacheSize + { + get + { + // if this is the first query, we need to load the cache size from somewhere + if (_currentCacheSize == 0) + { + // Read the system file for cache size + object cacheSizeObj = ReadSysFile(CacheSizeFile); + + // Did we successfully get data from the file? + if (cacheSizeObj != null) + { + _currentCacheSize = (long)cacheSizeObj; + } + } + + return _currentCacheSize; + } + private set + { + // no need to do a pointless re-store of the same value + if (_currentCacheSize != value || value == 0) + { + WriteSysFile(CacheSizeFile, value); + _currentCacheSize = value; + } + } + } + + /// + /// Event that will be called when is reached. + /// + public event EventHandler MaxCacheSizeReached = delegate { }; + + public event EventHandler CacheResized = delegate { }; + + /// + /// The default cache path used by FC. + /// + private string DefaultCachePath + { + get + { + return Directory.GetCurrentDirectory(); + } + } + + #region constructors + + /// + /// Creates a default instance of the file cache. Don't use if you plan to serialize custom objects + /// + /// If true, will calcualte the cache's current size upon new object creation. + /// Turned off by default as directory traversal is somewhat expensive and may not always be necessary based on + /// use case. + /// + /// If supplied, sets the interval of time that must occur between self cleans + public FileCache(bool calculateCacheSize = false, TimeSpan cleanInterval = new TimeSpan()) + { + // CT note: I moved this code to an init method because if the user specified a cache root, that needs to + // be set before checking if we should clean (otherwise it will look for the file in the wrong place) + Init(calculateCacheSize, cleanInterval); + } + + /// + /// Creates an instance of the file cache using the supplied path as the root save path. + /// + /// The cache's root file path + /// If true, will calcualte the cache's current size upon new object creation. + /// Turned off by default as directory traversal is somewhat expensive and may not always be necessary based on + /// use case. + /// + /// If supplied, sets the interval of time that must occur between self cleans + public FileCache(string cacheRoot, bool calculateCacheSize = false, TimeSpan cleanInterval = new TimeSpan()) + { + CacheDir = cacheRoot; + Init(calculateCacheSize, cleanInterval, false); + } + + /// + /// Creates an instance of the file cache. + /// + /// The SerializationBinder used to deserialize cached objects. Needed if you plan + /// to cache custom objects. + /// + /// If true, will calcualte the cache's current size upon new object creation. + /// Turned off by default as directory traversal is somewhat expensive and may not always be necessary based on + /// use case. + /// + /// If supplied, sets the interval of time that must occur between self cleans + public FileCache(SerializationBinder binder, bool calculateCacheSize = false, TimeSpan cleanInterval = new TimeSpan()) + { + _binder = binder; + Init(calculateCacheSize, cleanInterval, true, false); + } + + /// + /// Creates an instance of the file cache. + /// + /// The cache's root file path + /// The SerializationBinder used to deserialize cached objects. Needed if you plan + /// to cache custom objects. + /// If true, will calcualte the cache's current size upon new object creation. + /// Turned off by default as directory traversal is somewhat expensive and may not always be necessary based on + /// use case. + /// + /// If supplied, sets the interval of time that must occur between self cleans + public FileCache(string cacheRoot, SerializationBinder binder, bool calculateCacheSize = false, TimeSpan cleanInterval = new TimeSpan()) + { + _binder = binder; + CacheDir = cacheRoot; + Init(calculateCacheSize, cleanInterval, false, false); + } + + #endregion + + #region custom methods + + private void Init(bool calculateCacheSize = false, TimeSpan cleanInterval = new TimeSpan(), bool setCacheDirToDefault = true, bool setBinderToDefault = true) + { + _name = "FileCache_" + _nameCounter; + _nameCounter++; + + DefaultRegion = null; + DefaultPolicy = new CacheItemPolicy(); + AccessTimeout = new TimeSpan(); + MaxCacheSize = long.MaxValue; + + // set default values if not already set + if (setCacheDirToDefault) + CacheDir = DefaultCachePath; + if (setBinderToDefault) + _binder = new FileCacheBinder(); + + // if it doesn't exist, we need to make it + if (!Directory.Exists(CacheDir)) + Directory.CreateDirectory(CacheDir); + + // only set the clean interval if the user supplied it + if (cleanInterval > new TimeSpan()) + { + _cleanInterval = cleanInterval; + } + + //check to see if cache is in need of immediate cleaning + if (ShouldClean()) + { + CleanCacheAsync(); + } + else if (calculateCacheSize || CurrentCacheSize == 0) + { + // This is in an else if block, because CleanCacheAsync will + // update the cache size, so no need to do it twice. + UpdateCacheSizeAsync(); + } + + MaxCacheSizeReached += FileCache_MaxCacheSizeReached; + } + + private void FileCache_MaxCacheSizeReached(object sender, FileCacheEventArgs e) + { + Task.Factory.StartNew((Action)(() => + { + // Shrink the cache to 75% of the max size + // that way there's room for it to grow a bit + // before we have to do this again. + long newSize = ShrinkCacheToSize((long)(MaxCacheSize * 0.75)); + })); + } + + + // Returns the cleanlock file if it can be opened, otherwise it is being used by another process so return null + private FileStream GetCleaningLock() + { + try + { + return File.Open(Path.Combine(CacheDir, SemaphoreFile), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None); + } + catch (Exception) + { + return null; + } + } + + // Determines whether or not enough time has passed that the cache should clean itself + private bool ShouldClean() + { + try + { + // if the file can't be found, or is corrupt this will throw an exception + DateTime? lastClean = ReadSysFile(LastCleanedDateFile) as DateTime?; + + //AC: rewrote to be safer in null cases + if (lastClean == null) + { + return true; + } + + // return true if the amount of time between now and the last clean is greater than or equal to the + // clean interval, otherwise return false. + return DateTime.Now - lastClean >= _cleanInterval; + } + catch (Exception) + { + return true; + } + } + + /// + /// Shrinks the cache until the cache size is less than + /// or equal to the size specified (in bytes). This is a + /// rather expensive operation, so use with discretion. + /// + /// The new size of the cache + public long ShrinkCacheToSize(long newSize, string regionName = null) + { + long originalSize = 0, amount = 0, removed = 0; + + //lock down other treads from trying to shrink or clean + using (FileStream cLock = GetCleaningLock()) + { + if (cLock == null) + return -1; + + // if we're shrinking the whole cache, we can use the stored + // size if it's available. If it's not available we calculate it and store + // it for next time. + if (regionName == null) + { + if (CurrentCacheSize == 0) + { + CurrentCacheSize = GetCacheSize(); + } + + originalSize = CurrentCacheSize; + } + else + { + originalSize = GetCacheSize(regionName); + } + + // Find out how much we need to get rid of + amount = originalSize - newSize; + + // CT note: This will update CurrentCacheSize + removed = DeleteOldestFiles(amount, regionName); + + // unlock the semaphore for others + cLock.Close(); + } + + // trigger the event + CacheResized(this, new FileCacheEventArgs(originalSize - removed, MaxCacheSize)); + + // return the final size of the cache (or region) + return originalSize - removed; + } + + public void CleanCacheAsync() + { + Task.Factory.StartNew((Action)(() => + { + CleanCache(); + })); + } + + /// + /// Loop through the cache and delete all expired files + /// + /// The amount removed (in bytes) + public long CleanCache(string regionName = null) + { + long removed = 0; + + //lock down other treads from trying to shrink or clean + using (FileStream cLock = GetCleaningLock()) + { + if (cLock == null) + return 0; + + foreach (string key in GetKeys(regionName)) + { + CacheItemPolicy policy = GetPolicy(key, regionName); + if (policy.AbsoluteExpiration < DateTime.Now) + { + try + { + string cachePath = GetCachePath(key, regionName); + string policyPath = GetPolicyPath(key, regionName); + CacheItemReference ci = new CacheItemReference(key, cachePath, policyPath); + Remove(key, regionName); // CT note: Remove will update CurrentCacheSize + removed += ci.Length; + } + catch (Exception) // skip if the file cannot be accessed + { } + } + } + + // mark that we've cleaned the cache + WriteSysFile(LastCleanedDateFile, DateTime.Now); + + // unlock + cLock.Close(); + } + + return removed; + } + + public void ClearRegion(string regionName) + { + using (var cLock = GetCleaningLock()) + { + if (cLock == null) + return; + + foreach (var key in GetKeys(regionName)) + { + Remove(key, regionName); + } + + cLock.Close(); + } + } + + /// + /// Delete the oldest items in the cache to shrink the chache by the + /// specified amount (in bytes). + /// + /// The amount of data that was actually removed + private long DeleteOldestFiles(long amount, string regionName = null) + { + // Verify that we actually need to shrink + if (amount <= 0) + { + return 0; + } + + //Heap of all CacheReferences + PriortyQueue cacheReferences = new PriortyQueue(); + + //build a heap of all files in cache region + foreach (string key in GetKeys(regionName)) + { + try + { + //build item reference + string cachePath = GetCachePath(key, regionName); + string policyPath = GetPolicyPath(key, regionName); + CacheItemReference ci = new CacheItemReference(key, cachePath, policyPath); + cacheReferences.Enqueue(ci); + } + catch (FileNotFoundException) + { + } + } + + //remove cache items until size requirement is met + long removedBytes = 0; + while (removedBytes < amount && cacheReferences.GetSize() > 0) + { + //remove oldest item + CacheItemReference oldest = cacheReferences.Dequeue(); + removedBytes += oldest.Length; + Remove(oldest.Key, regionName); + } + return removedBytes; + } + + /// + /// This method calls GetCacheSize on a separate thread to + /// calculate and then store the size of the cache. + /// + public void UpdateCacheSizeAsync() + { + Task.Factory.StartNew((Action)(() => + { + CurrentCacheSize = GetCacheSize(); + })); + } + + //AC Note: From MSDN / SO (http://stackoverflow.com/questions/468119/whats-the-best-way-to-calculate-the-size-of-a-directory-in-net) + /// + /// Calculates the size, in bytes of the file cache + /// + /// The region to calculate. If NULL, will return total size. + /// + public long GetCacheSize(string regionName = null) + { + long size = 0; + + //AC note: First parameter is unused, so just pass in garbage ("DummyValue") + string policyPath = Path.GetDirectoryName(GetPolicyPath("DummyValue", regionName)); + string cachePath = Path.GetDirectoryName(GetCachePath("DummyValue", regionName)); + size += CacheSizeHelper(new DirectoryInfo(policyPath)); + size += CacheSizeHelper(new DirectoryInfo(cachePath)); + return size; + } + + /// + /// Helper method for public . + /// + /// + /// + private long CacheSizeHelper(DirectoryInfo root) + { + long size = 0; + + // Add file sizes. + var fis = root.EnumerateFiles(); + foreach (FileInfo fi in fis) + { + size += fi.Length; + } + // Add subdirectory sizes. + var dis = root.EnumerateDirectories(); + foreach (DirectoryInfo di in dis) + { + size += CacheSizeHelper(di); + } + return size; + } + + /// + /// Flushes the file cache using DateTime.Now as the minimum date + /// + /// + public void Flush(string regionName = null) + { + Flush(DateTime.Now, regionName); + } + + /// + /// Flushes the cache based on last access date, filtered by optional region + /// + /// + /// + public void Flush(DateTime minDate, string regionName = null) + { + // prevent other threads from altering stuff while we delete junk + using (FileStream cLock = GetCleaningLock()) + { + if (cLock == null) + return; + + //AC note: First parameter is unused, so just pass in garbage ("DummyValue") + string policyPath = Path.GetDirectoryName(GetPolicyPath("DummyValue", regionName)); + string cachePath = Path.GetDirectoryName(GetCachePath("DummyValue", regionName)); + FlushHelper(new DirectoryInfo(policyPath), minDate); + FlushHelper(new DirectoryInfo(cachePath), minDate); + + // Update the Cache size + CurrentCacheSize = GetCacheSize(); + + // unlock + cLock.Close(); + } + } + + /// + /// Helper method for public flush + /// + /// + /// + private void FlushHelper(DirectoryInfo root, DateTime minDate) + { + // check files. + foreach (FileInfo fi in root.EnumerateFiles()) + { + //is the file stale? + if (minDate > File.GetLastAccessTime(fi.FullName)) + { + File.Delete(fi.FullName); + } + } + + // check subdirectories + foreach (DirectoryInfo di in root.EnumerateDirectories()) + { + FlushHelper(di, minDate); + } + } + + /// + /// Returns the policy attached to a given cache item. + /// + /// The key of the item + /// The region in which the key exists + /// + public CacheItemPolicy GetPolicy(string key, string regionName = null) + { + CacheItemPolicy policy = new CacheItemPolicy(); + FileCachePayload payload = ReadFile(PayloadMode.Filename, key, regionName) as FileCachePayload; + if (payload != null) + { + try + { + policy.SlidingExpiration = payload.Policy.SlidingExpiration; + policy.AbsoluteExpiration = payload.Policy.AbsoluteExpiration; + } + catch (Exception) + { + } + } + return policy; + } + + /// + /// Returns a list of keys for a given region. + /// + /// + /// + public IEnumerable GetKeys(string regionName = null) + { + string region = ""; + if (string.IsNullOrEmpty(regionName) == false) + { + region = regionName; + } + string directory = Path.Combine(CacheDir, _cacheSubFolder, region); + if (Directory.Exists(directory)) + { + foreach (string file in Directory.EnumerateFiles(directory)) + { + yield return Path.GetFileNameWithoutExtension(file); + } + } + } + + #endregion + + #region helper methods + + /// + /// This function servies to centralize file stream access within this class. + /// + /// + /// + /// + /// + /// + private FileStream GetStream(string path, FileMode mode, FileAccess access, FileShare share) + { + FileStream stream = null; + TimeSpan interval = new TimeSpan(0, 0, 0, 0, 50); + TimeSpan totalTime = new TimeSpan(); + while (stream == null) + { + try + { + stream = File.Open(path, mode, access, share); + } + catch (IOException ex) + { + Thread.Sleep(interval); + totalTime += interval; + + //if we've waited too long, throw the original exception. + if (AccessTimeout.Ticks != 0) + { + if (totalTime > AccessTimeout) + { + throw ex; + } + } + } + } + return stream; + } + + /// + /// This function serves to centralize file reads within this class. + /// + /// the payload reading mode + /// + /// + /// + private FileCachePayload ReadFile(PayloadMode mode, string key, string regionName = null, SerializationBinder objectBinder = null) + { + object data = null; + SerializableCacheItemPolicy policy = new SerializableCacheItemPolicy(); + string cachePath = GetCachePath(key, regionName); + string policyPath = GetPolicyPath(key, regionName); + FileCachePayload payload = new FileCachePayload(null); + + if (File.Exists(cachePath)) + { + switch (mode) + { + default: + case PayloadMode.Filename: + data = cachePath; + break; + case PayloadMode.Serializable: + data = DeserializePayloadData(objectBinder, cachePath); + break; + case PayloadMode.RawBytes: + data = LoadRawPayloadData(cachePath); + break; + } + } + if (File.Exists(policyPath)) + { + using (FileStream stream = GetStream(policyPath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + BinaryFormatter formatter = new BinaryFormatter(); + formatter.Binder = new LocalCacheBinder(); + try + { + policy = formatter.Deserialize(stream) as SerializableCacheItemPolicy; + } + catch (SerializationException) + { + policy = new SerializableCacheItemPolicy(); + } + } + } + payload.Payload = data; + payload.Policy = policy; + return payload; + } + + private object LoadRawPayloadData(string cachePath) + { + throw new NotSupportedException("Reading raw payload is not currently supported."); + } + + private object DeserializePayloadData(SerializationBinder objectBinder, string cachePath) + { + object data; + using (FileStream stream = GetStream(cachePath, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + BinaryFormatter formatter = new BinaryFormatter(); + + //AC: From http://spazzarama.com//2009/06/25/binary-deserialize-unable-to-find-assembly/ + // Needed to deserialize custom objects + if (objectBinder != null) + { + //take supplied binder over default binder + formatter.Binder = objectBinder; + } + else if (_binder != null) + { + formatter.Binder = _binder; + } + + try + { + data = formatter.Deserialize(stream); + } + catch (SerializationException) + { + data = null; + } + } + + return data; + } + + /// + /// This function serves to centralize file writes within this class + /// + private void WriteFile(PayloadMode mode, string key, FileCachePayload data, string regionName = null, bool policyUpdateOnly = false) + { + string cachedPolicy = GetPolicyPath(key, regionName); + string cachedItemPath = GetCachePath(key, regionName); + + + if (!policyUpdateOnly) + { + long oldBlobSize = 0; + if (File.Exists(cachedItemPath)) + { + oldBlobSize = new FileInfo(cachedItemPath).Length; + } + + switch (mode) + { + case PayloadMode.Serializable: + using (FileStream stream = GetStream(cachedItemPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + + BinaryFormatter formatter = new BinaryFormatter(); + formatter.Serialize(stream, data.Payload); + } + break; + case PayloadMode.RawBytes: + using (FileStream stream = GetStream(cachedItemPath, FileMode.Create, FileAccess.Write, FileShare.None)) + { + + if (data.Payload is byte[]) + { + byte[] dataPayload = (byte[])data.Payload; + stream.Write(dataPayload, 0, dataPayload.Length); + } + else if (data.Payload is Stream) + { + Stream dataPayload = (Stream)data.Payload; + dataPayload.CopyTo(stream); + // no close or the like, we are not the owner + } + } + break; + + case PayloadMode.Filename: + File.Copy((string)data.Payload, cachedItemPath, true); + break; + } + + //adjust cache size (while we have the file to ourselves) + CurrentCacheSize += new FileInfo(cachedItemPath).Length - oldBlobSize; + } + + //remove current policy file from cache size calculations + if (File.Exists(cachedPolicy)) + { + CurrentCacheSize -= new FileInfo(cachedPolicy).Length; + } + + //write the cache policy + using (FileStream stream = GetStream(cachedPolicy, FileMode.Create, FileAccess.Write, FileShare.None)) + { + BinaryFormatter formatter = new BinaryFormatter(); + formatter.Serialize(stream, data.Policy); + + // adjust cache size + CurrentCacheSize += new FileInfo(cachedPolicy).Length; + + stream.Close(); + } + + //check to see if limit was reached + if (CurrentCacheSize > MaxCacheSize) + { + MaxCacheSizeReached(this, new FileCacheEventArgs(CurrentCacheSize, MaxCacheSize)); + } + } + + /// + /// Reads data in from a system file. System files are not part of the + /// cache itself, but serve as a way for the cache to store data it + /// needs to operate. + /// + /// The name of the sysfile (without directory) + /// The data from the file + private object ReadSysFile(string filename) + { + // sys files go in the root directory + string path = Path.Combine(CacheDir, filename); + object data = null; + + if (File.Exists(path)) + { + for (int i = 5; i > 0; i--) // try 5 times to read the file, if we can't, give up + { + try + { + using (FileStream stream = GetStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + BinaryFormatter formatter = new BinaryFormatter(); + try + { + data = formatter.Deserialize(stream); + } + catch (Exception) + { + data = null; + } + finally + { + stream.Close(); + } + } + break; + } + catch (IOException) + { + // we timed out... so try again + } + } + } + + return data; + } + + /// + /// Writes data to a system file that is not part of the cache itself, + /// but is used to help it function. + /// + /// The name of the sysfile (without directory) + /// The data to write to the file + private void WriteSysFile(string filename, object data) + { + // sys files go in the root directory + string path = Path.Combine(CacheDir, filename); + + // write the data to the file + using (FileStream stream = GetStream(path, FileMode.Create, FileAccess.Write, FileShare.Write)) + { + BinaryFormatter formatter = new BinaryFormatter(); + formatter.Serialize(stream, data); + stream.Close(); + } + } + + /// + /// Builds a string that will place the specified file name within the appropriate + /// cache and workspace folder. + /// + /// + /// + /// + private string GetCachePath(string FileName, string regionName = null) + { + if (regionName == null) + { + regionName = ""; + } + string directory = Path.Combine(CacheDir, _cacheSubFolder, regionName); + string filePath = Path.Combine(directory, Path.GetFileNameWithoutExtension(FileName) + ".dat"); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + return filePath; + } + + /// + /// Builds a string that will get the path to the supplied file's policy file + /// + /// + /// + /// + private string GetPolicyPath(string FileName, string regionName = null) + { + if (regionName == null) + { + regionName = ""; + } + string directory = Path.Combine(CacheDir, _policySubFolder, regionName); + string filePath = Path.Combine(directory, Path.GetFileNameWithoutExtension(FileName) + ".policy"); + if (!Directory.Exists(directory)) + { + Directory.CreateDirectory(directory); + } + return filePath; + } + + #endregion + + #region ObjectCache overrides + + public override object AddOrGetExisting(string key, object value, CacheItemPolicy policy, string regionName = null) + { + string path = GetCachePath(key, regionName); + object oldData = null; + + //pull old value if it exists + if (File.Exists(path)) + { + try + { + oldData = Get(key, regionName); + } + catch (Exception) + { + oldData = null; + } + } + SerializableCacheItemPolicy cachePolicy = new SerializableCacheItemPolicy(policy); + FileCachePayload newPayload = new FileCachePayload(value, cachePolicy); + WriteFile(PayloadWriteMode, key, newPayload, regionName); + + //As documented in the spec (http://msdn.microsoft.com/en-us/library/dd780602.aspx), return the old + //cached value or null + return oldData; + } + + public override CacheItem AddOrGetExisting(CacheItem value, CacheItemPolicy policy) + { + object oldData = AddOrGetExisting(value.Key, value.Value, policy, value.RegionName); + CacheItem returnItem = null; + if (oldData != null) + { + returnItem = new CacheItem(value.Key) + { + Value = oldData, + RegionName = value.RegionName + }; + } + return returnItem; + } + + public override object AddOrGetExisting(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) + { + CacheItemPolicy policy = new CacheItemPolicy(); + policy.AbsoluteExpiration = absoluteExpiration; + return AddOrGetExisting(key, value, policy, regionName); + } + + public override bool Contains(string key, string regionName = null) + { + string path = GetCachePath(key, regionName); + return File.Exists(path); + } + + public override CacheEntryChangeMonitor CreateCacheEntryChangeMonitor(IEnumerable keys, string regionName = null) + { + throw new NotImplementedException(); + } + + public override DefaultCacheCapabilities DefaultCacheCapabilities + { + get + { + //AC note: can use boolean OR "|" to set multiple flags. + return DefaultCacheCapabilities.CacheRegions + | + DefaultCacheCapabilities.AbsoluteExpirations + | + DefaultCacheCapabilities.SlidingExpirations + ; + } + } + + public override object Get(string key, string regionName = null) + { + FileCachePayload payload = ReadFile(PayloadReadMode, key, regionName) as FileCachePayload; + string cachedItemPath = GetCachePath(key, regionName); + + DateTime cutoff = DateTime.Now; + if (PayloadReadMode == PayloadMode.Filename) + { + cutoff += FilenameAsPayloadSafetyMargin; + } + + //null payload? + if (payload != null) + { + //did the item expire? + if (payload.Policy.AbsoluteExpiration < cutoff) + { + //set the payload to null + payload.Payload = null; + + //delete the file from the cache + try + { + // CT Note: I changed this to Remove from File.Delete so that the coresponding + // policy file will be deleted as well, and CurrentCacheSize will be updated. + Remove(key, regionName); + } + catch (Exception) + { + } + } + else + { + //does the item have a sliding expiration? + if (payload.Policy.SlidingExpiration > new TimeSpan()) + { + payload.Policy.AbsoluteExpiration = DateTime.Now.Add(payload.Policy.SlidingExpiration); + WriteFile(PayloadWriteMode, cachedItemPath, payload, regionName, true); + } + + } + } + else + { + //remove null payload + Remove(key, regionName); + + //create dummy one for return + payload = new FileCachePayload(null); + } + return payload.Payload; + } + + public override CacheItem GetCacheItem(string key, string regionName = null) + { + object value = Get(key, regionName); + CacheItem item = new CacheItem(key); + item.Value = value; + item.RegionName = regionName; + return item; + } + + public override long GetCount(string regionName = null) + { + if (regionName == null) + { + regionName = ""; + } + string path = Path.Combine(CacheDir, _cacheSubFolder, regionName); + if (Directory.Exists(path)) + return Directory.GetFiles(path).Count(); + else + return 0; + } + + /// + /// Returns an enumerator for the specified region (defaults to base-level cache directory). + /// This function *WILL NOT* recursively locate files in subdirectories. + /// + /// + /// + public IEnumerator> GetEnumerator(string regionName = null) + { + string region = ""; + if (string.IsNullOrEmpty(regionName) == false) + { + region = regionName; + } + List> enumerator = new List>(); + + string directory = Path.Combine(CacheDir, _cacheSubFolder, region); + foreach (string filePath in Directory.EnumerateFiles(directory)) + { + string key = Path.GetFileNameWithoutExtension(filePath); + enumerator.Add(new KeyValuePair(key, this.Get(key, regionName))); + } + return enumerator.GetEnumerator(); + } + + /// + /// Will return an enumerator with all cache items listed in the root file path ONLY. Use the other + /// if you want to specify a region + /// + /// + protected override IEnumerator> GetEnumerator() + { + return GetEnumerator(null); + } + + public override IDictionary GetValues(IEnumerable keys, string regionName = null) + { + Dictionary values = new Dictionary(); + foreach (string key in keys) + { + values[key] = Get(key, regionName); + } + return values; + } + + public override string Name + { + get { return _name; } + } + + public override object Remove(string key, string regionName = null) + { + object valueToDelete = null; + if (Contains(key, regionName)) + { + // Because of the possibility of multiple threads accessing this, it's possible that + // while we're trying to remove something, another thread has already removed it. + try + { + //remove cache entry + // CT note: calling Get from remove leads to an infinite loop and stack overflow, + // so I replaced it with a simple ReadFile call. None of the code here actually + // uses this object returned, but just in case someone else's outside code does. + FileCachePayload fcp = ReadFile(PayloadMode.Filename, key, regionName); + valueToDelete = fcp.Payload; + string path = GetCachePath(key, regionName); + CurrentCacheSize -= new FileInfo(path).Length; + File.Delete(path); + + //remove policy file + string cachedPolicy = GetPolicyPath(key, regionName); + CurrentCacheSize -= new FileInfo(cachedPolicy).Length; + File.Delete(cachedPolicy); + } + catch (IOException) + { + } + + } + return valueToDelete; + } + + public override void Set(string key, object value, CacheItemPolicy policy, string regionName = null) + { + Add(key, value, policy, regionName); + } + + public override void Set(CacheItem item, CacheItemPolicy policy) + { + Add(item, policy); + } + + public override void Set(string key, object value, DateTimeOffset absoluteExpiration, string regionName = null) + { + Add(key, value, absoluteExpiration, regionName); + } + + public override object this[string key] + { + get + { + return this.Get(key, DefaultRegion); + } + set + { + this.Set(key, value, DefaultPolicy, DefaultRegion); + } + } + + #endregion + + private class LocalCacheBinder : System.Runtime.Serialization.SerializationBinder + { + public override Type BindToType(string assemblyName, string typeName) + { + Type typeToDeserialize = null; + + String currentAssembly = Assembly.GetAssembly(typeof(LocalCacheBinder)).FullName; + assemblyName = currentAssembly; + + // Get the type using the typeName and assemblyName + typeToDeserialize = Type.GetType(String.Format("{0}, {1}", + typeName, assemblyName)); + + return typeToDeserialize; + } + } + + // CT: This private class is used to help shrink the cache. + // It computes the total size of an entry including it's policy file. + // It also implements IComparable functionality to allow for sorting based on access time + private class CacheItemReference : IComparable + { + public readonly DateTime LastAccessTime; + public readonly long Length; + public readonly string Key; + + public CacheItemReference(string key, string cachePath, string policyPath) + { + Key = key; + FileInfo cfi = new FileInfo(cachePath); + FileInfo pfi = new FileInfo(policyPath); + cfi.Refresh(); + LastAccessTime = cfi.LastAccessTime; + Length = cfi.Length + pfi.Length; + } + + public int CompareTo(CacheItemReference other) + { + int i = LastAccessTime.CompareTo(other.LastAccessTime); + + // It's possible, although rare, that two different items will have + // the same LastAccessTime. So in that case, we need to check to see + // if they're actually the same. + if (i == 0) + { + // second order should be length (but from smallest to largest, + // that way we delete smaller files first) + i = -1 * Length.CompareTo(other.Length); + if (i == 0) + { + i = Key.CompareTo(other.Key); + } + } + + return i; + } + + public static bool operator >(CacheItemReference lhs, CacheItemReference rhs) + { + if (lhs.CompareTo(rhs) > 0) + { + return true; + } + return false; + } + + public static bool operator <(CacheItemReference lhs, CacheItemReference rhs) + { + if (lhs.CompareTo(rhs) < 0) + { + return true; + } + return false; + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.Api/Caching/FileCacheBinder.cs b/src/GitHub.Api/Caching/FileCacheBinder.cs new file mode 100644 index 0000000000..dd03649603 --- /dev/null +++ b/src/GitHub.Api/Caching/FileCacheBinder.cs @@ -0,0 +1,34 @@ +/* +Copyright 2012, 2013, 2017 Adam Carter (http://adam-carter.com) + +This file is part of FileCache (http://github.com/acarteas/FileCache). + +FileCache is distributed under the Apache License 2.0. +Consult "LICENSE.txt" included in this package for the Apache License 2.0. +*/ +using System.Reflection; + +namespace System.Runtime.Caching +{ + /// + /// You should be able to copy & paste this code into your local project to enable caching custom objects. + /// + public sealed class FileCacheBinder : System.Runtime.Serialization.SerializationBinder + { + public override Type BindToType(string assemblyName, string typeName) + { + Type typeToDeserialize = null; + + String currentAssembly = Assembly.GetExecutingAssembly().FullName; + + // In this case we are always using the current assembly + assemblyName = currentAssembly; + + // Get the type using the typeName and assemblyName + typeToDeserialize = Type.GetType(String.Format("{0}, {1}", + typeName, assemblyName)); + + return typeToDeserialize; + } + } +} \ No newline at end of file diff --git a/src/GitHub.Api/Caching/FileCacheEventArgs.cs b/src/GitHub.Api/Caching/FileCacheEventArgs.cs new file mode 100644 index 0000000000..917ff89e95 --- /dev/null +++ b/src/GitHub.Api/Caching/FileCacheEventArgs.cs @@ -0,0 +1,22 @@ +/* +Copyright 2012, 2013, 2017 Adam Carter (http://adam-carter.com) + +This file is part of FileCache (http://github.com/acarteas/FileCache). + +FileCache is distributed under the Apache License 2.0. +Consult "LICENSE.txt" included in this package for the Apache License 2.0. +*/ + +namespace System.Runtime.Caching +{ + public class FileCacheEventArgs : EventArgs + { + public long CurrentCacheSize { get; private set; } + public long MaxCacheSize { get; private set; } + public FileCacheEventArgs(long currentSize, long maxSize) + { + CurrentCacheSize = currentSize; + MaxCacheSize = maxSize; + } + } +} \ No newline at end of file diff --git a/src/GitHub.Api/Caching/FileCachePayload.cs b/src/GitHub.Api/Caching/FileCachePayload.cs new file mode 100644 index 0000000000..1361e6f663 --- /dev/null +++ b/src/GitHub.Api/Caching/FileCachePayload.cs @@ -0,0 +1,33 @@ +/* +Copyright 2012, 2013, 2017 Adam Carter (http://adam-carter.com) + +This file is part of FileCache (http://github.com/acarteas/FileCache). + +FileCache is distributed under the Apache License 2.0. +Consult "LICENSE.txt" included in this package for the Apache License 2.0. +*/ + +namespace System.Runtime.Caching +{ + [Serializable] + public class FileCachePayload + { + public object Payload { get; set; } + public SerializableCacheItemPolicy Policy { get; set; } + + public FileCachePayload(object payload) + { + Payload = payload; + Policy = new SerializableCacheItemPolicy() + { + AbsoluteExpiration = DateTime.Now.AddYears(10) + }; + } + + public FileCachePayload(object payload, SerializableCacheItemPolicy policy) + { + Payload = payload; + Policy = policy; + } + } +} \ No newline at end of file diff --git a/src/GitHub.Api/Caching/PriortyQueue.cs b/src/GitHub.Api/Caching/PriortyQueue.cs new file mode 100644 index 0000000000..cda897b9d5 --- /dev/null +++ b/src/GitHub.Api/Caching/PriortyQueue.cs @@ -0,0 +1,207 @@ +/* +Copyright 2012, 2013, 2017 Adam Carter (http://adam-carter.com) + +This file is part of FileCache (http://github.com/acarteas/FileCache). + +FileCache is distributed under the Apache License 2.0. +Consult "LICENSE.txt" included in this package for the Apache License 2.0. +*/ +using System.Collections.Generic; + +namespace System.Runtime.Caching +{ + /// + /// A basic min priorty queue (min heap) + /// + /// Data type to store + public class PriortyQueue where T : IComparable + { + + private List _items; + private IComparer _comparer; + + /// + /// Default constructor. + /// + /// The comparer to use. The default comparer will make the smallest item the root of the heap. + /// + /// + public PriortyQueue(IComparer comparer = null) + { + _items = new List(); + if (comparer == null) + { + _comparer = new GenericComparer(); + } + } + + /// + /// Constructor that will convert an existing list into a min heap + /// + /// The unsorted list of items + /// The comparer to use. The default comparer will make the smallest item the root of the heap. + public PriortyQueue(List unsorted, IComparer comparer = null) + : this(comparer) + { + for (int i = 0; i < unsorted.Count; i++) + { + _items.Add(unsorted[i]); + } + BuildHeap(); + } + + private void BuildHeap() + { + for (int i = _items.Count / 2; i >= 0; i--) + { + adjustHeap(i); + } + } + + //Percolates the item specified at by index down into its proper location within a heap. Used + //for dequeue operations and array to heap conversions + private void adjustHeap(int index) + { + //cannot percolate empty list + if (_items.Count == 0) + { + return; + } + + //GOAL: get value at index, make sure this value is less than children + // IF NOT: swap with smaller of two + // (continue to do so until we can't swap) + T item = _items[index]; + + //helps us figure out if a given index has children + int end_location = _items.Count; + + //keeps track of smallest index + int smallest_index = index; + + //while we're not the last thing in the heap + while (index < end_location) + { + //get left child index + int left_child_index = (2 * index) + 1; + int right_child_index = left_child_index + 1; + + //Three cases: + // 1. left index is out of range + // 2. right index is out or range + // 3. both indices are valid + if (left_child_index < end_location) + { + //CASE 1 is FALSE + //remember that left index is the smallest + smallest_index = left_child_index; + + if (right_child_index < end_location) + { + //CASE 2 is FALSE (CASE 3 is true) + //TODO: find value of smallest index + smallest_index = (_comparer.Compare(_items[left_child_index], _items[right_child_index]) < 0) + ? left_child_index + : right_child_index; + } + } + + //we have two things: original index and (potentially) a child index + if (_comparer.Compare(_items[index], _items[smallest_index]) > 0) + { + //move parent down (it was too big) + T temp = _items[index]; + _items[index] = _items[smallest_index]; + _items[smallest_index] = temp; + + //update index + index = smallest_index; + } + else + { + //no swap necessary + break; + } + } + } + + public bool isEmpty() + { + return _items.Count == 0; + } + + public int GetSize() + { + return _items.Count; + } + + + public void Enqueue(T item) + { + //calculate positions + int current_position = _items.Count; + int parent_position = (current_position - 1) / 2; + + //insert element (note: may get erased if we hit the WHILE loop) + _items.Add(item); + + //find parent, but be careful if we are an empty queue + T parent = default(T); + if (parent_position >= 0) + { + //find parent + parent = _items[parent_position]; + + //bubble up until we're done + while (_comparer.Compare(parent, item) > 0 && current_position > 0) + { + //move parent down + _items[current_position] = parent; + + //recalculate position + current_position = parent_position; + parent_position = (current_position - 1) / 2; + + //make sure that we have a valid index + if (parent_position >= 0) + { + //find parent + parent = _items[parent_position]; + } + } + } //end check for nullptr + + //after WHILE loop, current_position will point to the place that + //variable "item" needs to go + _items[current_position] = item; + + } + + public T GetFirst() + { + return _items[0]; + } + + public T Dequeue() + { + int last_position = _items.Count - 1; + T last_item = _items[last_position]; + T top = _items[0]; + _items[0] = last_item; + _items.RemoveAt(_items.Count - 1); + + //percolate down + adjustHeap(0); + return top; + } + + + private class GenericComparer : IComparer where TInner : IComparable + { + public int Compare(TInner x, TInner y) + { + return x.CompareTo(y); + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.Api/Caching/SerializableCacheItemPolicy.cs b/src/GitHub.Api/Caching/SerializableCacheItemPolicy.cs new file mode 100644 index 0000000000..a3a22f5c54 --- /dev/null +++ b/src/GitHub.Api/Caching/SerializableCacheItemPolicy.cs @@ -0,0 +1,44 @@ +/* +Copyright 2012, 2013, 2017 Adam Carter (http://adam-carter.com) + +This file is part of FileCache (http://github.com/acarteas/FileCache). + +FileCache is distributed under the Apache License 2.0. +Consult "LICENSE.txt" included in this package for the Apache License 2.0. +*/ + +namespace System.Runtime.Caching +{ + [Serializable] + public class SerializableCacheItemPolicy + { + public DateTimeOffset AbsoluteExpiration { get; set; } + + private TimeSpan _slidingExpiration; + public TimeSpan SlidingExpiration + { + get + { + return _slidingExpiration; + } + set + { + _slidingExpiration = value; + if (_slidingExpiration > new TimeSpan()) + { + AbsoluteExpiration = DateTimeOffset.Now.Add(_slidingExpiration); + } + } + } + public SerializableCacheItemPolicy(CacheItemPolicy policy) + { + AbsoluteExpiration = policy.AbsoluteExpiration; + SlidingExpiration = policy.SlidingExpiration; + } + + public SerializableCacheItemPolicy() + { + SlidingExpiration = new TimeSpan(); + } + } +} \ No newline at end of file diff --git a/src/GitHub.Api/GitHub.Api.csproj b/src/GitHub.Api/GitHub.Api.csproj index 6ff35a4ff0..421201ab15 100644 --- a/src/GitHub.Api/GitHub.Api.csproj +++ b/src/GitHub.Api/GitHub.Api.csproj @@ -4,22 +4,53 @@ full true - + + + + + 2454a3e6102fd41cc212 + 2157c138e970165d955d09562230afcfbcda23f2 + + - - - - ApiClientConfiguration_User.cs - - - + + + + $(IntermediateOutputPath)ApiClientConfiguration.$(GitHubVS_ClientId).cs + + + + + + + + + + + + + @@ -30,6 +61,5 @@ - diff --git a/src/GitHub.Api/GlobalSuppressions.cs b/src/GitHub.Api/GlobalSuppressions.cs new file mode 100644 index 0000000000..3753e44dee --- /dev/null +++ b/src/GitHub.Api/GlobalSuppressions.cs @@ -0,0 +1,9 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Reliability", "CA2007:Do not directly await a Task", Justification = "Discouraged for VSSDK projects.")] + diff --git a/src/GitHub.Api/GraphQLClient.cs b/src/GitHub.Api/GraphQLClient.cs new file mode 100644 index 0000000000..155bd2bd07 --- /dev/null +++ b/src/GitHub.Api/GraphQLClient.cs @@ -0,0 +1,157 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Runtime.Caching; +using System.Security.Cryptography; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using GitHub.Extensions; +using Octokit.GraphQL; +using Octokit.GraphQL.Core; + +namespace GitHub.Api +{ + public class GraphQLClient : IGraphQLClient + { + public static readonly TimeSpan DefaultCacheDuration = TimeSpan.FromHours(8); + readonly IConnection connection; + readonly FileCache cache; + + public GraphQLClient( + IConnection connection, + FileCache cache) + { + this.connection = connection; + this.cache = cache; + } + + public Task ClearCache(string regionName) + { + // Switch to background thread because FileCache does not provide an async API. + return Task.Run(() => cache.ClearRegion(GetFullRegionName(regionName))); + } + + public Task Run( + IQueryableValue query, + Dictionary variables = null, + bool refresh = false, + TimeSpan? cacheDuration = null, + string regionName = null, + CancellationToken cancellationToken = default) + { + return Run(query.Compile(), variables, refresh, cacheDuration, regionName, cancellationToken); + } + + public Task> Run( + IQueryableList query, + Dictionary variables = null, + bool refresh = false, + TimeSpan? cacheDuration = null, + string regionName = null, + CancellationToken cancellationToken = default) + { + return Run(query.Compile(), variables, refresh, cacheDuration, regionName, cancellationToken); + } + + public async Task Run( + ICompiledQuery query, + Dictionary variables = null, + bool refresh = false, + TimeSpan? cacheDuration = null, + string regionName = null, + CancellationToken cancellationToken = default) + { + if (!query.IsMutation) + { + var wrapper = new CachingWrapper( + this, + refresh, + cacheDuration ?? DefaultCacheDuration, + GetFullRegionName(regionName)); + return await wrapper.Run(query, variables, cancellationToken); + } + else + { + return await connection.Run(query, variables, cancellationToken); + } + } + + string GetFullRegionName(string regionName) + { + var result = connection.Uri.Host; + + if (!string.IsNullOrWhiteSpace(regionName)) + { + result += Path.DirectorySeparatorChar + regionName; + } + + return result.EnsureValidPath(); + } + + static string GetHash(string input) + { + var sb = new StringBuilder(); + + using (var hash = SHA256.Create()) + { + var result = hash.ComputeHash(Encoding.UTF8.GetBytes(input)); + + foreach (var b in result) + { + sb.Append(b.ToString("x2", CultureInfo.InvariantCulture)); + } + } + + return sb.ToString(); + } + + class CachingWrapper : IConnection + { + readonly GraphQLClient owner; + readonly bool refresh; + readonly TimeSpan cacheDuration; + readonly string regionName; + + public CachingWrapper( + GraphQLClient owner, + bool refresh, + TimeSpan cacheDuration, + string regionName) + { + this.owner = owner; + this.refresh = refresh; + this.cacheDuration = cacheDuration; + this.regionName = regionName; + } + + public Uri Uri => owner.connection.Uri; + + public Task Run(string query, CancellationToken cancellationToken = default) + { + // Switch to background thread because FileCache does not provide an async API. + return Task.Run(async () => + { + var hash = GetHash(query); + + if (refresh) + { + owner.cache.Remove(hash, regionName); + } + + var data = (string) owner.cache.Get(hash, regionName); + + if (data != null) + { + return data; + } + + var result = await owner.connection.Run(query, cancellationToken); + owner.cache.Add(hash, result, DateTimeOffset.Now + cacheDuration, regionName); + return result; + }, cancellationToken); + } + } + } +} diff --git a/src/GitHub.Api/GraphQLClientFactory.cs b/src/GitHub.Api/GraphQLClientFactory.cs index cd91295935..635467a845 100644 --- a/src/GitHub.Api/GraphQLClientFactory.cs +++ b/src/GitHub.Api/GraphQLClientFactory.cs @@ -1,6 +1,9 @@ using System; using System.ComponentModel.Composition; +using System.IO; +using System.Runtime.Caching; using System.Threading.Tasks; +using GitHub.Info; using GitHub.Models; using GitHub.Primitives; using Octokit.GraphQL; @@ -17,6 +20,7 @@ public class GraphQLClientFactory : IGraphQLClientFactory { readonly IKeychain keychain; readonly IProgram program; + readonly FileCache cache; /// /// Initializes a new instance of the class. @@ -28,14 +32,21 @@ public GraphQLClientFactory(IKeychain keychain, IProgram program) { this.keychain = keychain; this.program = program; + + var cachePath = Path.Combine( + Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), + ApplicationInfo.ApplicationName, + "GraphQLCache"); + cache = new FileCache(cachePath); } /// - public Task CreateConnection(HostAddress address) + public Task CreateConnection(HostAddress address) { var credentials = new GraphQLKeychainCredentialStore(keychain, address); var header = new ProductHeaderValue(program.ProductHeader.Name, program.ProductHeader.Version); - return Task.FromResult(new Connection(header, address.GraphQLUri, credentials)); + var connection = new Connection(header, address.GraphQLUri, credentials); + return Task.FromResult(new GraphQLClient(connection, cache)); } } } diff --git a/src/GitHub.Api/IGraphQLClient.cs b/src/GitHub.Api/IGraphQLClient.cs new file mode 100644 index 0000000000..d45062c6b4 --- /dev/null +++ b/src/GitHub.Api/IGraphQLClient.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Octokit.GraphQL; +using Octokit.GraphQL.Core; + +namespace GitHub.Api +{ + public interface IGraphQLClient + { + Task ClearCache(string regionName); + + Task Run( + IQueryableValue query, + Dictionary variables = null, + bool refresh = false, + TimeSpan? cacheDuration = null, + string regionName = null, + CancellationToken cancellationToken = default); + + Task> Run( + IQueryableList query, + Dictionary variables = null, + bool refresh = false, + TimeSpan? cacheDuration = null, + string regionName = null, + CancellationToken cancellationToken = default); + + Task Run( + ICompiledQuery query, + Dictionary variables = null, + bool refresh = false, + TimeSpan? cacheDuration = null, + string regionName = null, + CancellationToken cancellationToken = default); + } +} \ No newline at end of file diff --git a/src/GitHub.Api/IGraphQLClientFactory.cs b/src/GitHub.Api/IGraphQLClientFactory.cs index 464fab0de8..f29fba4b7f 100644 --- a/src/GitHub.Api/IGraphQLClientFactory.cs +++ b/src/GitHub.Api/IGraphQLClientFactory.cs @@ -4,16 +4,15 @@ namespace GitHub.Api { /// - /// Creates GraphQL s for querying the - /// GitHub GraphQL API. + /// Creates s for querying the GitHub GraphQL API. /// public interface IGraphQLClientFactory { /// - /// Creates a new . + /// Creates a new . /// /// The address of the server. - /// A task returning the created connection. - Task CreateConnection(HostAddress address); + /// A task returning the created client. + Task CreateConnection(HostAddress address); } } \ No newline at end of file diff --git a/src/GitHub.Api/IOAuthCallbackListener.cs b/src/GitHub.Api/IOAuthCallbackListener.cs index 4c8d29b85c..19a7c8f428 100644 --- a/src/GitHub.Api/IOAuthCallbackListener.cs +++ b/src/GitHub.Api/IOAuthCallbackListener.cs @@ -16,5 +16,11 @@ public interface IOAuthCallbackListener /// A cancellation token. /// The temporary code included in the callback. Task Listen(string id, CancellationToken cancel); + + /// + /// Redirects the last context to respond with listen and stops the underlying http listener + /// + /// Url to redirect to. + void RedirectLastContext(Uri url); } } diff --git a/src/GitHub.Api/LoginManager.cs b/src/GitHub.Api/LoginManager.cs index 0ffd81a77d..bdfe84c2c9 100644 --- a/src/GitHub.Api/LoginManager.cs +++ b/src/GitHub.Api/LoginManager.cs @@ -157,6 +157,8 @@ public async Task LoginViaOAuth( await keychain.Save("[oauth]", token.AccessToken, hostAddress).ConfigureAwait(false); var result = await ReadUserWithRetry(client).ConfigureAwait(false); await keychain.Save(result.User.Login, token.AccessToken, hostAddress).ConfigureAwait(false); + oauthListener.RedirectLastContext(hostAddress.WebUri.Append(result.User.Login)); + return result; } @@ -289,7 +291,7 @@ async Task HandleTwoFactorAuthorization( } } - ApplicationAuthorization EnsureNonNullAuthorization(ApplicationAuthorization auth) + static ApplicationAuthorization EnsureNonNullAuthorization(ApplicationAuthorization auth) { // If a mock IGitHubClient is not set up correctly, it can return null from // IGutHubClient.Authorization.Create - this will cause an infinite loop in Login() @@ -344,9 +346,14 @@ async Task GetUserAndCheckScopes(IGitHubClient client) var response = await client.Connection.Get( UserEndpoint, null, null).ConfigureAwait(false); - if (response.HttpResponse.Headers.ContainsKey(ScopesHeader)) + var scopes = response.HttpResponse.Headers + .Where(h => string.Equals(h.Key, ScopesHeader, StringComparison.OrdinalIgnoreCase)) + .Select(h => h.Value) + .FirstOrDefault(); + + if (scopes != null) { - var returnedScopes = new ScopesCollection(response.HttpResponse.Headers[ScopesHeader] + var returnedScopes = new ScopesCollection(scopes .Split(',') .Select(x => x.Trim()) .ToArray()); diff --git a/src/GitHub.App/Api/ApiClient.cs b/src/GitHub.App/Api/ApiClient.cs index f9fe4055ee..56257e82a3 100644 --- a/src/GitHub.App/Api/ApiClient.cs +++ b/src/GitHub.App/Api/ApiClient.cs @@ -18,7 +18,6 @@ namespace GitHub.Api { public partial class ApiClient : IApiClient { - const string ScopesHeader = "X-OAuth-Scopes"; const string ProductName = Info.ApplicationInfo.ApplicationDescription; static readonly ILogger log = LogManager.ForContext(); diff --git a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs index 74214a1741..1c0fbb3cb9 100644 --- a/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs +++ b/src/GitHub.App/Authentication/TwoFactorChallengeHandler.cs @@ -6,9 +6,11 @@ using ReactiveUI; using System.Threading.Tasks; using GitHub.Api; -using GitHub.Helpers; using GitHub.Extensions; using GitHub.ViewModels.Dialog; +using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.Shell; +using Task = System.Threading.Tasks.Task; namespace GitHub.Authentication { @@ -17,6 +19,12 @@ namespace GitHub.Authentication [PartCreationPolicy(CreationPolicy.Shared)] public class TwoFactorChallengeHandler : ReactiveObject, IDelegatingTwoFactorChallengeHandler { + [ImportingConstructor] + public TwoFactorChallengeHandler([Import(AllowDefault = true)] JoinableTaskContext joinableTaskContext) + { + JoinableTaskContext = joinableTaskContext ?? ThreadHelper.JoinableTaskContext; + } + ILogin2FaViewModel twoFactorDialog; public IViewModel CurrentViewModel { @@ -33,7 +41,7 @@ public async Task HandleTwoFactorException(TwoFactorAu { Guard.ArgumentNotNull(exception, nameof(exception)); - await ThreadingHelper.SwitchToMainThreadAsync(); + await JoinableTaskContext.Factory.SwitchToMainThreadAsync(); var userError = new TwoFactorRequiredUserError(exception); var result = await twoFactorDialog.Show(userError); @@ -50,8 +58,10 @@ public async Task HandleTwoFactorException(TwoFactorAu public async Task ChallengeFailed(Exception exception) { - await ThreadingHelper.SwitchToMainThreadAsync(); + await JoinableTaskContext.Factory.SwitchToMainThreadAsync(); twoFactorDialog.Cancel(); } + + JoinableTaskContext JoinableTaskContext { get; } } } \ No newline at end of file diff --git a/src/GitHub.App/Collections/VirtualizingList.cs b/src/GitHub.App/Collections/VirtualizingList.cs index b283c3f56f..1e4f1edb4f 100644 --- a/src/GitHub.App/Collections/VirtualizingList.cs +++ b/src/GitHub.App/Collections/VirtualizingList.cs @@ -12,6 +12,10 @@ using GitHub.Logging; using Serilog; +#pragma warning disable CA1010 // Collections should implement generic interface +#pragma warning disable CA1033 // Interface methods should be callable by child types +#pragma warning disable CA1710 // Identifiers should have correct suffix + namespace GitHub.Collections { /// diff --git a/src/GitHub.App/Collections/VirtualizingListCollectionView.cs b/src/GitHub.App/Collections/VirtualizingListCollectionView.cs index 9aa5900b24..1cd16b8f13 100644 --- a/src/GitHub.App/Collections/VirtualizingListCollectionView.cs +++ b/src/GitHub.App/Collections/VirtualizingListCollectionView.cs @@ -4,6 +4,10 @@ using System.Collections.Specialized; using System.Windows.Data; +#pragma warning disable CA1010 // Collections should implement generic interface +#pragma warning disable CA1033 // Interface methods should be callable by child types +#pragma warning disable CA1710 // Identifiers should have correct suffix + namespace GitHub.Collections { /// diff --git a/src/GitHub.App/Factories/ModelServiceFactory.cs b/src/GitHub.App/Factories/ModelServiceFactory.cs index a5ef967749..5ff0c3b1ba 100644 --- a/src/GitHub.App/Factories/ModelServiceFactory.cs +++ b/src/GitHub.App/Factories/ModelServiceFactory.cs @@ -8,6 +8,7 @@ using GitHub.Models; using GitHub.Services; using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; namespace GitHub.Factories { @@ -25,11 +26,13 @@ public sealed class ModelServiceFactory : IModelServiceFactory, IDisposable public ModelServiceFactory( IApiClientFactory apiClientFactory, IHostCacheFactory hostCacheFactory, - IAvatarProvider avatarProvider) + IAvatarProvider avatarProvider, + [Import(AllowDefault = true)] JoinableTaskContext joinableTaskContext) { this.apiClientFactory = apiClientFactory; this.hostCacheFactory = hostCacheFactory; this.avatarProvider = avatarProvider; + JoinableTaskContext = joinableTaskContext ?? ThreadHelper.JoinableTaskContext; } public async Task CreateAsync(IConnection connection) @@ -60,9 +63,11 @@ await hostCacheFactory.Create(connection.HostAddress), public IModelService CreateBlocking(IConnection connection) { - return ThreadHelper.JoinableTaskFactory.Run(() => CreateAsync(connection)); + return JoinableTaskContext.Factory.Run(() => CreateAsync(connection)); } public void Dispose() => cacheLock.Dispose(); + + JoinableTaskContext JoinableTaskContext { get; } } } diff --git a/src/GitHub.App/GitHub.App.csproj b/src/GitHub.App/GitHub.App.csproj index a6f5be28c8..d9b0ef5354 100644 --- a/src/GitHub.App/GitHub.App.csproj +++ b/src/GitHub.App/GitHub.App.csproj @@ -25,6 +25,8 @@ + + @@ -40,19 +42,21 @@ - - - + + - - - - + - + + + + true + + + \ No newline at end of file diff --git a/src/GitHub.App/GlobalSuppressions.cs b/src/GitHub.App/GlobalSuppressions.cs index 8e2e306555..997cb18a85 100644 --- a/src/GitHub.App/GlobalSuppressions.cs +++ b/src/GitHub.App/GlobalSuppressions.cs @@ -1,12 +1,3 @@ -using System.Diagnostics.CodeAnalysis; - -[assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "GitHub.ViewModels.CreateRepoViewModel.#ResetState()")] -[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Git", Scope = "resource", Target = "GitHub.Resources.resources")] -[assembly: SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "GitHub.Caches.CredentialCache.#InsertObject`1(System.String,!!0,System.Nullable`1)")] -[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Git", Scope = "resource", Target = "GitHub.App.Resources.resources")] -[assembly: SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object,System.Object)", Scope = "member", Target = "GitHub.Services.PullRequestService.#CreateTempFile(System.String,System.String,System.String)")] -[assembly: SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object,System.Object)", Scope = "member", Target = "GitHub.Services.PullRequestService.#CreateTempFile(System.String,System.String,System.String,System.Text.Encoding)")] - // This file is used by Code Analysis to maintain SuppressMessage // attributes that are applied to this project. // Project-level suppressions either have no target or are given @@ -16,3 +7,15 @@ // Code Analysis results, point to "Suppress Message", and click // "In Suppression File". // You do not need to add suppressions to this file manually. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Scope = "member", Target = "GitHub.ViewModels.CreateRepoViewModel.#ResetState()")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Git", Scope = "resource", Target = "GitHub.Resources.resources")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1800:DoNotCastUnnecessarily", Scope = "member", Target = "GitHub.Caches.CredentialCache.#InsertObject`1(System.String,!!0,System.Nullable`1)")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "Git", Scope = "resource", Target = "GitHub.App.Resources.resources")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object,System.Object)", Scope = "member", Target = "GitHub.Services.PullRequestService.#CreateTempFile(System.String,System.String,System.String)")] +[assembly: SuppressMessage("Microsoft.Globalization", "CA1305:SpecifyIFormatProvider", MessageId = "System.String.Format(System.String,System.Object,System.Object,System.Object)", Scope = "member", Target = "GitHub.Services.PullRequestService.#CreateTempFile(System.String,System.String,System.String,System.Text.Encoding)")] +[assembly: SuppressMessage("Design", "CA1056:Uri properties should not be strings")] +[assembly: SuppressMessage("Design", "CA1054:Uri parameters should not be strings")] +[assembly: SuppressMessage("Reliability", "CA2007:Do not directly await a Task", Justification = "Discouraged for VSSDK projects.")] diff --git a/src/GitHub.App/Models/PullRequestModel.cs b/src/GitHub.App/Models/PullRequestModel.cs index 6a560fa2ea..c1f6bd3837 100644 --- a/src/GitHub.App/Models/PullRequestModel.cs +++ b/src/GitHub.App/Models/PullRequestModel.cs @@ -110,8 +110,8 @@ public string Title } } - PullRequestStateEnum status; - public PullRequestStateEnum State + PullRequestState status; + public PullRequestState State { get { return status; } set @@ -126,8 +126,8 @@ public PullRequestStateEnum State } // TODO: Remove these property once maintainer workflow has been merged to master. - public bool IsOpen => State == PullRequestStateEnum.Open; - public bool Merged => State == PullRequestStateEnum.Merged; + public bool IsOpen => State == PullRequestState.Open; + public bool Merged => State == PullRequestState.Merged; int commentCount; public int CommentCount diff --git a/src/GitHub.App/Models/SuggestionItem.cs b/src/GitHub.App/Models/SuggestionItem.cs new file mode 100644 index 0000000000..713e0357e9 --- /dev/null +++ b/src/GitHub.App/Models/SuggestionItem.cs @@ -0,0 +1,51 @@ +using System; +using GitHub.Extensions; +using GitHub.Helpers; + +namespace GitHub.Models +{ + /// + /// Represents a single auto completion suggestion (mentions, emojis, issues) in a generic format that can be + /// easily cached. + /// + public class SuggestionItem + { + public SuggestionItem(string name, string description) + { + Guard.ArgumentNotEmptyString(name, "name"); + Guard.ArgumentNotEmptyString(description, "description"); + + Name = name; + Description = description; + } + + public SuggestionItem(string name, string description, string imageUrl) + { + Guard.ArgumentNotEmptyString(name, "name"); + + Name = name; + Description = description; + ImageUrl = imageUrl; + } + + /// + /// The name to display for this entry + /// + public string Name { get; set; } + + /// + /// Additional details about the entry + /// + public string Description { get; set; } + + /// + /// An image url for this entry + /// + public string ImageUrl { get; set; } + + /// + /// The date this suggestion was last modified according to the API. + /// + public DateTimeOffset? LastModifiedDate { get; set; } + } +} diff --git a/src/GitHub.App/Properties/AssemblyInfo.cs b/src/GitHub.App/Properties/AssemblyInfo.cs index 20fe17f77f..0ad9954b61 100644 --- a/src/GitHub.App/Properties/AssemblyInfo.cs +++ b/src/GitHub.App/Properties/AssemblyInfo.cs @@ -2,7 +2,9 @@ [assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.SampleData")] [assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.SampleData.Dialog.Clone")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.SampleData.Documents")] [assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.ViewModels")] [assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.ViewModels.Dialog")] [assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.ViewModels.Dialog.Clone")] +[assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.ViewModels.Documents")] [assembly: XmlnsDefinition("https://github.com/github/VisualStudio", "GitHub.ViewModels.GitHubPane")] diff --git a/src/GitHub.App/SampleData/CommentThreadViewModelDesigner.cs b/src/GitHub.App/SampleData/CommentThreadViewModelDesigner.cs index cd282d81c7..aaf13bafef 100644 --- a/src/GitHub.App/SampleData/CommentThreadViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/CommentThreadViewModelDesigner.cs @@ -1,5 +1,7 @@ -using System.Diagnostics.CodeAnalysis; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; +using GitHub.Models; using GitHub.ViewModels; using ReactiveUI; @@ -8,6 +10,16 @@ namespace GitHub.SampleData [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses")] public class CommentThreadViewModelDesigner : ViewModelBase, ICommentThreadViewModel { + public CommentThreadViewModelDesigner() + { + Comments = new ReactiveList(){new CommentViewModelDesigner() + { + Author = new ActorViewModel{ Login = "shana"}, + Body = "You can use a `CompositeDisposable` type here, it's designed to handle disposables in an optimal way (you can just call `Dispose()` on it and it will handle disposing everything it holds)." + }}; + + } + public IReadOnlyReactiveList Comments { get; } = new ReactiveList(); diff --git a/src/GitHub.App/SampleData/CommentViewModelDesigner.cs b/src/GitHub.App/SampleData/CommentViewModelDesigner.cs index 425f536ad7..132fe03568 100644 --- a/src/GitHub.App/SampleData/CommentViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/CommentViewModelDesigner.cs @@ -1,6 +1,9 @@ using System; using System.Diagnostics.CodeAnalysis; using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; using GitHub.ViewModels; using ReactiveUI; @@ -22,7 +25,9 @@ public CommentViewModelDesigner() public CommentEditState EditState { get; set; } public bool IsReadOnly { get; set; } public bool IsSubmitting { get; set; } + public bool CanCancel { get; } = true; public bool CanDelete { get; } = true; + public string CommitCaption { get; set; } = "Comment"; public ICommentThreadViewModel Thread { get; } public DateTimeOffset CreatedAt => DateTime.Now.Subtract(TimeSpan.FromDays(3)); public IActorViewModel Author { get; set; } @@ -31,7 +36,13 @@ public CommentViewModelDesigner() public ReactiveCommand BeginEdit { get; } public ReactiveCommand CancelEdit { get; } public ReactiveCommand CommitEdit { get; } - public ReactiveCommand OpenOnGitHub { get; } + public ReactiveCommand OpenOnGitHub { get; } = ReactiveCommand.Create(() => { }); public ReactiveCommand Delete { get; } + public IAutoCompleteAdvisor AutoCompleteAdvisor { get; } + + public Task InitializeAsync(ICommentThreadViewModel thread, ActorModel currentUser, CommentModel comment, CommentEditState state) + { + return Task.CompletedTask; + } } } diff --git a/src/GitHub.App/SampleData/Dialog/Clone/RepositoryCloneViewModelDesigner.cs b/src/GitHub.App/SampleData/Dialog/Clone/RepositoryCloneViewModelDesigner.cs index 4b2c599fe9..6ce69138d9 100644 --- a/src/GitHub.App/SampleData/Dialog/Clone/RepositoryCloneViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/Dialog/Clone/RepositoryCloneViewModelDesigner.cs @@ -2,6 +2,7 @@ using System.Reactive; using System.Threading.Tasks; using GitHub.Models; +using GitHub.Primitives; using GitHub.ViewModels; using GitHub.ViewModels.Dialog.Clone; using ReactiveUI; @@ -17,15 +18,16 @@ public RepositoryCloneViewModelDesigner() } public string Path { get; set; } + public UriString Url { get; set; } public string PathWarning { get; set; } public int SelectedTabIndex { get; set; } public string Title => null; public IObservable Done => null; public IRepositorySelectViewModel GitHubTab { get; } public IRepositorySelectViewModel EnterpriseTab { get; } - public IRepositoryUrlViewModel UrlTab { get; } public ReactiveCommand Browse { get; } public ReactiveCommand Clone { get; } + public ReactiveCommand LoginAsDifferentUser { get; } public Task InitializeAsync(IConnection connection) { diff --git a/src/GitHub.App/SampleData/Dialog/Clone/SelectPageViewModelDesigner.cs b/src/GitHub.App/SampleData/Dialog/Clone/SelectPageViewModelDesigner.cs index 82df1bfae9..14ad9d514e 100644 --- a/src/GitHub.App/SampleData/Dialog/Clone/SelectPageViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/Dialog/Clone/SelectPageViewModelDesigner.cs @@ -2,11 +2,13 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using System.Reactive; using System.Threading.Tasks; using System.Windows.Data; using GitHub.Models; using GitHub.ViewModels; using GitHub.ViewModels.Dialog.Clone; +using ReactiveUI; namespace GitHub.SampleData.Dialog.Clone { diff --git a/src/GitHub.App/SampleData/Documents/IssueishCommentThreadViewModelDesigner.cs b/src/GitHub.App/SampleData/Documents/IssueishCommentThreadViewModelDesigner.cs new file mode 100644 index 0000000000..888f09cd73 --- /dev/null +++ b/src/GitHub.App/SampleData/Documents/IssueishCommentThreadViewModelDesigner.cs @@ -0,0 +1,19 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.ViewModels; +using GitHub.ViewModels.Documents; +using ReactiveUI; + +namespace GitHub.SampleData.Documents +{ + public class IssueishCommentThreadViewModelDesigner : ViewModelBase, IIssueishCommentThreadViewModel + { + public IActorViewModel CurrentUser { get; } = new ActorViewModelDesigner("grokys"); + public Task InitializeAsync(ActorModel currentUser, IssueishDetailModel model, bool addPlaceholder) => Task.CompletedTask; + public Task DeleteComment(ICommentViewModel comment) => Task.CompletedTask; + public Task EditComment(ICommentViewModel comment) => Task.CompletedTask; + public Task PostComment(ICommentViewModel comment) => Task.CompletedTask; + public Task CloseOrReopen(ICommentViewModel comment) => Task.CompletedTask; + } +} diff --git a/src/GitHub.App/SampleData/Documents/PullRequestPageViewModelDesigner.cs b/src/GitHub.App/SampleData/Documents/PullRequestPageViewModelDesigner.cs new file mode 100644 index 0000000000..82ecf90d70 --- /dev/null +++ b/src/GitHub.App/SampleData/Documents/PullRequestPageViewModelDesigner.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.ViewModels; +using GitHub.ViewModels.Documents; +using ReactiveUI; + +namespace GitHub.SampleData.Documents +{ + public class PullRequestPageViewModelDesigner : ViewModelBase, IPullRequestPageViewModel + { + public PullRequestPageViewModelDesigner() + { + Body = @"Save drafts of inline comments, PR reviews and PRs. + +> Note: This feature required a refactoring of the comment view models because they now need async initialization and to be available from GitHub.App. This part of the PR has been submitted separately as #1993 to ease review. The two PRs can alternatively be reviewed as one if that's more convenient. + +As described in #1905, it is easy to lose a comment that you're working on if you close the diff view accidentally. This PR saves drafts of comments as they are being written to an SQLite database. + +In addition to saving drafts of inline comments, it also saves comments to PR reviews and PRs themselves. + +The comments are written to an SQLite database directly instead of going through Akavache because in the case of inline reviews, there can be many drafts in progress on a separate file. When a diff is opened we need to look for any comments present on that file and show the most recent. That use-case didn't fit well with Akavache (being a pure key/value store). + +## Testing + +### Inline Comments + +- Open a PR +- Open the diff of a file +- Start adding a comment +- Close the comment by closing the peek view, or the document tab +- Reopen the diff +- You should see the comment displayed in edit mode with the draft of the comment you were previously writing + +### PR reviews + +- Open a PR +- Click ""Add your review"" +- Start adding a review +- Click the ""Back"" button and navigate to a different PR +- Click the ""Back"" button and navigate to the original PR +- Click ""Add your review"" +- You should see the the draft of the review you were previously writing + +### PRs + +-Click ""Create new"" at the top of the PR list +- Start adding a PR title/ description +- Close VS +- Restart VS and click ""Create new"" again +- You should see the the draft of the PR you were previously writing + +Depends on #1993 +Fixes #1905"; + Timeline = new IViewModel[] + { + new CommitListViewModel( + new CommitSummaryViewModel(new CommitModel + { + Author = new CommitActorModel { User = new ActorModel{ Login = "grokys" }}, + AbbreviatedOid = "c7c7d25", + MessageHeadline = "Refactor comment view models." + }), + new CommitSummaryViewModel(new CommitModel + { + Author = new CommitActorModel { User = new ActorModel{ Login = "shana" }}, + AbbreviatedOid = "04e6a90", + MessageHeadline = "Refactor comment view models.", + })), + new CommentViewModelDesigner + { + Author = new ActorViewModelDesigner("meaghanlewis"), + Body = @"This is looking great! Really enjoying using this feature so far. + +When leaving an inline comment, the comment posts successfully and then a new comment is drafted with the same text.", + }, + new CommentViewModelDesigner + { + Author = new ActorViewModelDesigner("grokys"), + Body = @"Oops, sorry about that @meaghanlewis - I was sure I tested those things, but must have got messed up again at some point. Should be fixed now.", + }, + }; + } + + public string Id { get; set; } + public PullRequestState State { get; set; } = PullRequestState.Open; + public IReadOnlyList Timeline { get; } + public string SourceBranchDisplayName { get; set; } = "feature/save-drafts"; + public string TargetBranchDisplayName { get; set; } = "master"; + public IActorViewModel Author { get; set; } = new ActorViewModelDesigner("grokys"); + public int CommitCount { get; set; } = 2; + public string Body { get; set; } + public int Number { get; set; } = 1994; + public LocalRepositoryModel LocalRepository { get; } + public RemoteRepositoryModel Repository { get; set; } + public string Title { get; set; } = "Save drafts of comments"; + public Uri WebUrl { get; set; } + public ReactiveCommand OpenOnGitHub { get; } + public ReactiveCommand ShowCommit { get; } + + + public Task InitializeAsync(RemoteRepositoryModel repository, LocalRepositoryModel localRepository, ActorModel currentUser, PullRequestDetailModel model) + { + throw new NotImplementedException(); + } + } +} diff --git a/src/GitHub.App/SampleData/GitServiceDesigner.cs b/src/GitHub.App/SampleData/GitServiceDesigner.cs index cabbf30a4c..1e125d1a6e 100644 --- a/src/GitHub.App/SampleData/GitServiceDesigner.cs +++ b/src/GitHub.App/SampleData/GitServiceDesigner.cs @@ -15,5 +15,8 @@ class GitServiceDesigner : IGitService public IRepository GetRepository(string path) => null; public UriString GetUri(string path, string remote = "origin") => null; public UriString GetUri(IRepository repository, string remote = "origin") => null; + public Task Compare(IRepository repository, string sha1, string sha2, string relativePath) => null; + public Task CompareWith(IRepository repository, string sha1, string sha2, string path, byte[] contents) => null; + public Task Compare(IRepository repository, string sha1, string sha2, bool detectRenames = false) => null; } } diff --git a/src/GitHub.App/SampleData/InlineAnnotationViewModelDesigner.cs b/src/GitHub.App/SampleData/InlineAnnotationViewModelDesigner.cs new file mode 100644 index 0000000000..4aea77efb7 --- /dev/null +++ b/src/GitHub.App/SampleData/InlineAnnotationViewModelDesigner.cs @@ -0,0 +1,40 @@ +using System.Collections.Generic; +using GitHub.Models; +using GitHub.ViewModels; + +namespace GitHub.SampleData +{ + public class InlineAnnotationViewModelDesigner : IInlineAnnotationViewModel + { + public InlineAnnotationViewModelDesigner() + { + var checkRunAnnotationModel = new CheckRunAnnotationModel + { + AnnotationLevel = CheckAnnotationLevel.Failure, + Path = "SomeFile.cs", + EndLine = 12, + StartLine = 12, + Message = "Some Error Message", + Title = "CS12345" + }; + + var checkRunModel = + new CheckRunModel + { + Annotations = new List {checkRunAnnotationModel}, + Name = "Fake Check Run" + }; + + var checkSuiteModel = new CheckSuiteModel() + { + ApplicationName = "Fake Check Suite", + HeadSha = "ed6198c37b13638e902716252b0a17d54bd59e4a", + CheckRuns = new List { checkRunModel} + }; + + Model= new InlineAnnotationModel(checkSuiteModel, checkRunModel, checkRunAnnotationModel); + } + + public InlineAnnotationModel Model { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestAnnotationItemViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestAnnotationItemViewModelDesigner.cs new file mode 100644 index 0000000000..03f4e7d056 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestAnnotationItemViewModelDesigner.cs @@ -0,0 +1,18 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reactive; +using GitHub.Models; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + [ExcludeFromCodeCoverage] + public sealed class PullRequestAnnotationItemViewModelDesigner : IPullRequestAnnotationItemViewModel + { + public CheckRunAnnotationModel Annotation { get; set; } + public bool IsExpanded { get; set; } + public string LineDescription => $"{Annotation.StartLine}:{Annotation.EndLine}"; + public bool IsFileInPullRequest { get; set; } + public ReactiveCommand OpenAnnotation { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestAnnotationsViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestAnnotationsViewModelDesigner.cs new file mode 100644 index 0000000000..8e728ee8f3 --- /dev/null +++ b/src/GitHub.App/SampleData/PullRequestAnnotationsViewModelDesigner.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.ViewModels.GitHubPane; +using ReactiveUI; + +namespace GitHub.SampleData +{ + [ExcludeFromCodeCoverage] + public sealed class PullRequestAnnotationsViewModelDesigner : PanePageViewModelBase, IPullRequestAnnotationsViewModel + { + public LocalRepositoryModel LocalRepository { get; set; } + public string RemoteRepositoryOwner { get; set; } + public int PullRequestNumber { get; set; } = 123; + public string CheckRunId { get; set; } + public ReactiveCommand NavigateToPullRequest { get; } + public string PullRequestTitle { get; } = "Fixing stuff in this PR"; + public string CheckSuiteName { get; } = "Awesome Check Suite"; + public string CheckRunSummary { get; } = "Awesome Check Run Summary"; + public string CheckRunText { get; } = "Awesome Check Run Text"; + + public IReadOnlyDictionary AnnotationsDictionary { get; } + = new Dictionary + { + { + "asdf/asdf.cs", + new IPullRequestAnnotationItemViewModel[] + { + new PullRequestAnnotationItemViewModelDesigner + { + Annotation = new CheckRunAnnotationModel + { + AnnotationLevel = CheckAnnotationLevel.Warning, + StartLine = 3, + EndLine = 4, + Path = "asdf/asdf.cs", + Message = "; is expected", + Title = "CS 12345" + }, + IsExpanded = true, + IsFileInPullRequest = true + }, + new PullRequestAnnotationItemViewModelDesigner + { + Annotation = new CheckRunAnnotationModel + { + AnnotationLevel = CheckAnnotationLevel.Failure, + StartLine = 3, + EndLine = 4, + Path = "asdf/asdf.cs", + Message = "; is expected", + Title = "CS 12345" + }, + IsExpanded = true, + IsFileInPullRequest = true + }, + } + }, + { + "blah.cs", + new IPullRequestAnnotationItemViewModel[] + { + new PullRequestAnnotationItemViewModelDesigner + { + Annotation = new CheckRunAnnotationModel + { + AnnotationLevel = CheckAnnotationLevel.Notice, + StartLine = 3, + EndLine = 4, + Path = "blah.cs", + Message = "; is expected", + Title = "CS 12345" + }, + IsExpanded = true, + } + } + }, + }; + + public string CheckRunName { get; } = "Psuedo Check Run"; + + public Task InitializeAsync(LocalRepositoryModel localRepository, IConnection connection, string owner, + string repo, + int pullRequestNumber, string checkRunId) + { + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs index 3011e581c1..ec75bf27be 100644 --- a/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestCheckViewModelDesigner.cs @@ -1,6 +1,6 @@ using System; using System.Reactive; -using System.Windows.Media.Imaging; +using GitHub.Models; using GitHub.ViewModels; using GitHub.ViewModels.GitHubPane; using ReactiveUI; @@ -18,5 +18,11 @@ public sealed class PullRequestCheckViewModelDesigner : ViewModelBase, IPullRequ public Uri DetailsUrl { get; set; } = new Uri("http://github.com"); public ReactiveCommand OpenDetailsUrl { get; set; } = null; + + public PullRequestCheckType CheckType { get; set; } = PullRequestCheckType.ChecksApi; + + public string CheckRunId { get; set; } + + public bool HasAnnotations { get; } = true; } } \ No newline at end of file diff --git a/src/GitHub.App/SampleData/PullRequestCreationViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestCreationViewModelDesigner.cs index 32922438d8..77edc7ab89 100644 --- a/src/GitHub.App/SampleData/PullRequestCreationViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestCreationViewModelDesigner.cs @@ -4,6 +4,7 @@ using System.Reactive; using System.Threading.Tasks; using GitHub.Models; +using GitHub.Services; using GitHub.Validation; using GitHub.ViewModels.GitHubPane; using ReactiveUI; @@ -53,6 +54,7 @@ public PullRequestCreationViewModelDesigner() public string PRTitle { get; set; } public ReactivePropertyValidator TitleValidator { get; } + public IAutoCompleteAdvisor AutoCompleteAdvisor { get; } public ReactivePropertyValidator BranchValidator { get; } diff --git a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs index 0bb87d5d37..1b99d9b7bf 100644 --- a/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestDetailViewModelDesigner.cs @@ -9,6 +9,7 @@ using System.Reactive; using System.Threading.Tasks; using GitHub.SampleData; +using ReactiveUI.Legacy; namespace GitHub.SampleData { @@ -122,8 +123,11 @@ public PullRequestDetailViewModelDesigner() public ReactiveCommand Checkout { get; } public ReactiveCommand Pull { get; } public ReactiveCommand Push { get; } + public ReactiveCommand SyncSubmodules { get; } + public ReactiveCommand OpenConversation { get; } public ReactiveCommand OpenOnGitHub { get; } public ReactiveCommand ShowReview { get; } + public ReactiveCommand ShowAnnotations { get; } public IReadOnlyList Checks { get; } diff --git a/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs index b60b43bc03..5dda31c86d 100644 --- a/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestFilesViewModelDesigner.cs @@ -36,6 +36,9 @@ public PullRequestFilesViewModelDesigner() public ReactiveCommand DiffFileWithWorkingDirectory { get; } public ReactiveCommand OpenFileInWorkingDirectory { get; } public ReactiveCommand OpenFirstComment { get; } + public ReactiveCommand OpenFirstAnnotationNotice { get; } + public ReactiveCommand OpenFirstAnnotationWarning { get; } + public ReactiveCommand OpenFirstAnnotationFailure { get; } public Task InitializeAsync( IPullRequestSession session, diff --git a/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs index 500089fb84..38c6b5b740 100644 --- a/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestListItemViewModelDesigner.cs @@ -17,6 +17,9 @@ public class PullRequestListItemViewModelDesigner : ViewModelBase, IPullRequestL public int Number { get; set; } public string Title { get; set; } public DateTimeOffset UpdatedAt { get; set; } - public PullRequestChecksState Checks { get; set; } + public PullRequestChecksSummaryState ChecksSummary { get; set; } + public int ChecksPendingCount { get; set; } + public int ChecksSuccessCount { get; set; } + public int ChecksErrorCount { get; set; } } } diff --git a/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs index 46b572c5d9..146d35ed46 100644 --- a/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestListViewModelDesigner.cs @@ -63,6 +63,7 @@ public PullRequestListViewModelDesigner() public IReadOnlyList States { get; } public Uri WebUrl => null; public ReactiveCommand CreatePullRequest { get; } + public ReactiveCommand OpenConversation { get; } public ReactiveCommand OpenItem { get; } public ReactiveCommand OpenItemInBrowser { get; } diff --git a/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs index cfc8eb23e4..9d0c118cbb 100644 --- a/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs +++ b/src/GitHub.App/SampleData/PullRequestReviewAuthoringViewModelDesigner.cs @@ -3,6 +3,7 @@ using System.Reactive; using System.Threading.Tasks; using GitHub.Models; +using GitHub.Services; using GitHub.ViewModels.GitHubPane; using ReactiveUI; @@ -53,6 +54,7 @@ public PullRequestReviewAuthoringViewModelDesigner() public ReactiveCommand Comment { get; } public ReactiveCommand RequestChanges { get; } public ReactiveCommand Cancel { get; } + public IAutoCompleteAdvisor AutoCompleteAdvisor { get; } public Task InitializeAsync( LocalRepositoryModel localRepository, diff --git a/src/GitHub.App/SampleData/RepositoryRecloneViewModelDesigner.cs b/src/GitHub.App/SampleData/RepositoryRecloneViewModelDesigner.cs deleted file mode 100644 index 274f58d9c1..0000000000 --- a/src/GitHub.App/SampleData/RepositoryRecloneViewModelDesigner.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.Reactive; -using System.Threading.Tasks; -using System.Windows.Input; -using GitHub.Models; -using GitHub.Validation; -using GitHub.ViewModels; -using GitHub.ViewModels.Dialog; -using ReactiveUI; - -namespace GitHub.SampleData -{ - public class RepositoryRecloneViewModelDesigner : ViewModelBase, IRepositoryRecloneViewModel - { - public string Title { get; set; } - public string BaseRepositoryPath { get; set; } - public ReactivePropertyValidator BaseRepositoryPathValidator { get; } - public ICommand BrowseForDirectory { get; } - public ReactiveCommand CloneCommand { get; } - public RepositoryModel SelectedRepository { get; set; } - public IObservable Done { get; } - - public Task InitializeAsync(IConnection connection) => Task.CompletedTask; - } -} diff --git a/src/GitHub.App/SampleData/SampleViewModels.cs b/src/GitHub.App/SampleData/SampleViewModels.cs index 9a2a430e9f..77b36dc9c0 100644 --- a/src/GitHub.App/SampleData/SampleViewModels.cs +++ b/src/GitHub.App/SampleData/SampleViewModels.cs @@ -29,7 +29,6 @@ public RepositoryCreationViewModelDesigner() RepositoryName = "Hello-World"; Description = "A description"; KeepPrivate = true; - CanKeepPrivate = true; Accounts = new ReactiveList { new AccountDesigner { Login = "shana" }, @@ -80,12 +79,6 @@ public ReactiveCommand BrowseForDirectory private set; } - public bool CanKeepPrivate - { - get; - private set; - } - public ReactiveCommand CreateRepository { get; @@ -146,24 +139,6 @@ public IAccount SelectedAccount set; } - public bool ShowUpgradePlanWarning - { - get; - private set; - } - - public bool ShowUpgradeToMicroPlanWarning - { - get; - private set; - } - - public ICommand UpgradeAccountPlan - { - get; - private set; - } - public IReadOnlyList GitIgnoreTemplates { get; private set; @@ -239,6 +214,12 @@ public ReactiveCommand PublishRepository private set; } + public ReactiveCommand LoginAsDifferentUser + { + get; + private set; + } + public IReadOnlyObservableCollection Connections { get; diff --git a/src/GitHub.App/Services/AutoCompleteAdvisor.cs b/src/GitHub.App/Services/AutoCompleteAdvisor.cs new file mode 100644 index 0000000000..569a87b526 --- /dev/null +++ b/src/GitHub.App/Services/AutoCompleteAdvisor.cs @@ -0,0 +1,121 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.ComponentModel.Composition; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Reactive.Linq; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using Serilog; + +namespace GitHub.Services +{ + [Export(typeof(IAutoCompleteAdvisor))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class AutoCompleteAdvisor : IAutoCompleteAdvisor + { + const int SuggestionCount = 5; // The number of suggestions we'll provide. github.com does 5. + + static readonly ILogger log = LogManager.ForContext(); + readonly Lazy> prefixSourceMap; + + [ImportingConstructor] + public AutoCompleteAdvisor([ImportMany(typeof(IAutoCompleteSource))]IEnumerable autocompleteSources) + { + prefixSourceMap = new Lazy>( + () => autocompleteSources.ToDictionary(s => s.Prefix, s => s)); + } + + public IObservable GetAutoCompletionSuggestions(string text, int caretPosition) + { + Guard.ArgumentNotNull("text", text); + + if (caretPosition < 0 || caretPosition > text.Length) + { + string error = String.Format(CultureInfo.InvariantCulture, + "The CaretPosition '{0}', is not in the range of '0' and the text length '{1}' for the text '{2}'", + caretPosition, + text.Length, + text); + + // We need to be alerted when this happens because it should never happen. + // But it apparently did happen in production. + Debug.Fail(error); + log.Error(error); + return Observable.Empty(); + } + var tokenAndSource = PrefixSourceMap + .Select(kvp => new {Source = kvp.Value, Token = ParseAutoCompletionToken(text, caretPosition, kvp.Key)}) + .FirstOrDefault(s => s.Token != null); + + if (tokenAndSource == null) + { + return Observable.Return(AutoCompleteResult.Empty); + } + + return tokenAndSource.Source.GetSuggestions() + .Select(suggestion => new + { + suggestion, + rank = suggestion.GetSortRank(tokenAndSource.Token.SearchSearchPrefix) + }) + .Where(suggestion => suggestion.rank > -1) + .ToList() + .Select(suggestions => suggestions + .OrderByDescending(s => s.rank) + .ThenBy(s => s.suggestion.Name) + .Take(SuggestionCount) + .Select(s => s.suggestion) + .ToList()) + .Select(suggestions => new AutoCompleteResult(tokenAndSource.Token.Offset, + new ReadOnlyCollection(suggestions))) + .Catch(e => + { + log.Error(e, "Error Getting AutoCompleteResult"); + return Observable.Return(AutoCompleteResult.Empty); + }); + } + + [SuppressMessage("Microsoft.Usage", "CA2233:OperationsShouldNotOverflow", MessageId = "caretPosition-1" + , Justification = "We ensure the argument is greater than -1 so it can't overflow")] + public static AutoCompletionToken ParseAutoCompletionToken(string text, int caretPosition, string triggerPrefix) + { + Guard.ArgumentNotNull("text", text); + Guard.ArgumentInRange(caretPosition, 0, text.Length, "caretPosition"); + if (caretPosition == 0 || text.Length == 0) return null; + + // :th : 1 + //:th : 0 + //Hi :th : 3 + int beginningOfWord = text.LastIndexOfAny(new[] { ' ', '\n' }, caretPosition - 1) + 1; + string word = text.Substring(beginningOfWord, caretPosition - beginningOfWord); + if (!word.StartsWith(triggerPrefix, StringComparison.Ordinal)) return null; + + return new AutoCompletionToken(word.Substring(1), beginningOfWord); + } + + Dictionary PrefixSourceMap { get { return prefixSourceMap.Value; } } + } + + public class AutoCompletionToken + { + public AutoCompletionToken(string searchPrefix, int offset) + { + Guard.ArgumentNotNull(searchPrefix, "searchPrefix"); + Guard.ArgumentNonNegative(offset, "offset"); + + SearchSearchPrefix = searchPrefix; + Offset = offset; + } + + /// + /// Used to filter the list of auto complete suggestions to what the user has typed in. + /// + public string SearchSearchPrefix { get; private set; } + public int Offset { get; private set; } + } +} diff --git a/src/GitHub.App/Services/DialogService.cs b/src/GitHub.App/Services/DialogService.cs index ecce4a1cfa..5eeae61c74 100644 --- a/src/GitHub.App/Services/DialogService.cs +++ b/src/GitHub.App/Services/DialogService.cs @@ -2,6 +2,7 @@ using System.ComponentModel.Composition; using System.Threading.Tasks; using GitHub.Api; +using GitHub.Exports; using GitHub.Extensions; using GitHub.Factories; using GitHub.Models; @@ -16,25 +17,41 @@ public class DialogService : IDialogService { readonly IViewViewModelFactory factory; readonly IShowDialogService showDialog; + readonly IGitHubContextService gitHubContextService; [ImportingConstructor] public DialogService( IViewViewModelFactory factory, - IShowDialogService showDialog) + IShowDialogService showDialog, + IGitHubContextService gitHubContextService) { Guard.ArgumentNotNull(factory, nameof(factory)); Guard.ArgumentNotNull(showDialog, nameof(showDialog)); + Guard.ArgumentNotNull(showDialog, nameof(gitHubContextService)); this.factory = factory; this.showDialog = showDialog; + this.gitHubContextService = gitHubContextService; } public async Task ShowCloneDialog(IConnection connection, string url = null) { + if (string.IsNullOrEmpty(url)) + { + var clipboardContext = gitHubContextService.FindContextFromClipboard(); + switch (clipboardContext?.LinkType) + { + case LinkType.Blob: + case LinkType.Repository: + url = clipboardContext?.Url; + break; + } + } + var viewModel = factory.CreateViewModel(); if (url != null) { - viewModel.UrlTab.Url = url; + viewModel.Url = url; } if (connection != null) @@ -52,15 +69,6 @@ public async Task ShowCloneDialog(IConnection connection, str } } - public async Task ShowReCloneDialog(RepositoryModel repository) - { - Guard.ArgumentNotNull(repository, nameof(repository)); - - var viewModel = factory.CreateViewModel(); - viewModel.SelectedRepository = repository; - return (string)await showDialog.ShowWithFirstConnection(viewModel); - } - public async Task ShowCreateGist(IConnection connection) { var viewModel = factory.CreateViewModel(); diff --git a/src/GitHub.App/Services/EnterpriseCapabilitiesService.cs b/src/GitHub.App/Services/EnterpriseCapabilitiesService.cs index 072ed90228..5731a1f927 100644 --- a/src/GitHub.App/Services/EnterpriseCapabilitiesService.cs +++ b/src/GitHub.App/Services/EnterpriseCapabilitiesService.cs @@ -55,7 +55,7 @@ public async Task ProbeLoginMethods(Uri enterpriseBaseUr } } - private async Task GetMetadata(IConnection connection) + private static async Task GetMetadata(IConnection connection) { var endpoint = new Uri("meta", UriKind.Relative); var response = await connection.Get(endpoint, null, null).ConfigureAwait(false); diff --git a/src/GitHub.App/Services/ErrorMessageTranslator.cs b/src/GitHub.App/Services/ErrorMessageTranslator.cs index e3bdc423c6..91742b2fe1 100644 --- a/src/GitHub.App/Services/ErrorMessageTranslator.cs +++ b/src/GitHub.App/Services/ErrorMessageTranslator.cs @@ -7,6 +7,7 @@ using ReactiveUI.Legacy; #pragma warning disable CS0618 // Type or member is obsolete +#pragma warning disable CA1034 // Nested types should not be visible namespace GitHub.Services { diff --git a/src/GitHub.App/Services/FromGraphQlExtensions.cs b/src/GitHub.App/Services/FromGraphQlExtensions.cs index d56a63ae93..06a1c46e2e 100644 --- a/src/GitHub.App/Services/FromGraphQlExtensions.cs +++ b/src/GitHub.App/Services/FromGraphQlExtensions.cs @@ -1,6 +1,7 @@ using System; using GitHub.Models; using Octokit.GraphQL.Model; +using CheckAnnotationLevel = GitHub.Models.CheckAnnotationLevel; using CheckConclusionState = GitHub.Models.CheckConclusionState; using CheckStatusState = GitHub.Models.CheckStatusState; using PullRequestReviewState = GitHub.Models.PullRequestReviewState; @@ -28,21 +29,25 @@ public static class FromGraphQlExtensions return CheckConclusionState.Success; case Octokit.GraphQL.Model.CheckConclusionState.Neutral: return CheckConclusionState.Neutral; + case Octokit.GraphQL.Model.CheckConclusionState.Skipped: + return CheckConclusionState.Skipped; + case Octokit.GraphQL.Model.CheckConclusionState.Stale: + return CheckConclusionState.Stale; default: throw new ArgumentOutOfRangeException(nameof(value), value, null); } } - public static PullRequestStateEnum FromGraphQl(this PullRequestState value) + public static Models.PullRequestState FromGraphQl(this Octokit.GraphQL.Model.PullRequestState value) { switch (value) { - case PullRequestState.Open: - return PullRequestStateEnum.Open; - case PullRequestState.Closed: - return PullRequestStateEnum.Closed; - case PullRequestState.Merged: - return PullRequestStateEnum.Merged; + case Octokit.GraphQL.Model.PullRequestState.Open: + return Models.PullRequestState.Open; + case Octokit.GraphQL.Model.PullRequestState.Closed: + return Models.PullRequestState.Closed; + case Octokit.GraphQL.Model.PullRequestState.Merged: + return Models.PullRequestState.Merged; default: throw new ArgumentOutOfRangeException(nameof(value), value, null); } @@ -84,7 +89,7 @@ public static CheckStatusState FromGraphQl(this Octokit.GraphQL.Model.CheckStatu } } - public static GitHub.Models.PullRequestReviewState FromGraphQl(this Octokit.GraphQL.Model.PullRequestReviewState value) + public static PullRequestReviewState FromGraphQl(this Octokit.GraphQL.Model.PullRequestReviewState value) { switch (value) { case Octokit.GraphQL.Model.PullRequestReviewState.Pending: @@ -101,5 +106,20 @@ public static GitHub.Models.PullRequestReviewState FromGraphQl(this Octokit.Grap throw new ArgumentOutOfRangeException(nameof(value), value, null); } } + + public static CheckAnnotationLevel FromGraphQl(this Octokit.GraphQL.Model.CheckAnnotationLevel value) + { + switch (value) + { + case Octokit.GraphQL.Model.CheckAnnotationLevel.Failure: + return CheckAnnotationLevel.Failure; + case Octokit.GraphQL.Model.CheckAnnotationLevel.Notice: + return CheckAnnotationLevel.Notice; + case Octokit.GraphQL.Model.CheckAnnotationLevel.Warning: + return CheckAnnotationLevel.Warning; + default: + throw new ArgumentOutOfRangeException(nameof(value), value, null); + } + } } } \ No newline at end of file diff --git a/src/GitHub.App/Services/GitClient.cs b/src/GitHub.App/Services/GitClient.cs index 6114d82169..96d22e6d97 100644 --- a/src/GitHub.App/Services/GitClient.cs +++ b/src/GitHub.App/Services/GitClient.cs @@ -17,7 +17,6 @@ namespace GitHub.Services [PartCreationPolicy(CreationPolicy.Shared)] public class GitClient : IGitClient { - const string defaultOriginName = "origin"; static readonly ILogger log = LogManager.ForContext(); readonly IGitService gitService; readonly PullOptions pullOptions; @@ -44,12 +43,17 @@ public GitClient(IGitHubCredentialProvider credentialProvider, IGitService gitSe public Task Pull(IRepository repository) { Guard.ArgumentNotNull(repository, nameof(repository)); - return Task.Factory.StartNew(() => + return Task.Run(() => { var signature = repository.Config.BuildSignature(DateTimeOffset.UtcNow); -#pragma warning disable 0618 // TODO: Replace `Network.Pull` with `Commands.Pull`. - repository.Network.Pull(signature, pullOptions); -#pragma warning restore 0618 + if (repository is Repository repo) + { + LibGit2Sharp.Commands.Pull(repo, signature, pullOptions); + } + else + { + log.Error("Couldn't pull because {Variable} isn't an instance of {Type}", nameof(repository), typeof(Repository)); + } }); } @@ -59,7 +63,7 @@ public Task Push(IRepository repository, string branchName, string remoteName) Guard.ArgumentNotEmptyString(branchName, nameof(branchName)); Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); - return Task.Factory.StartNew(() => + return Task.Run(() => { if (repository.Head?.Commits != null && repository.Head.Commits.Any()) { @@ -70,30 +74,6 @@ public Task Push(IRepository repository, string branchName, string remoteName) }); } - public Task Fetch(IRepository repository, string remoteName) - { - Guard.ArgumentNotNull(repository, nameof(repository)); - Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); - - return Task.Factory.StartNew(() => - { - try - { - var remote = repository.Network.Remotes[remoteName]; -#pragma warning disable 0618 // TODO: Replace `Network.Fetch` with `Commands.Fetch`. - repository.Network.Fetch(remote, fetchOptions); -#pragma warning restore 0618 - } - catch (Exception ex) - { - log.Error(ex, "Failed to fetch"); -#if DEBUG - throw; -#endif - } - }); - } - public Task Fetch(IRepository repo, UriString cloneUrl, params string[] refspecs) { foreach (var remote in repo.Network.Remotes) @@ -104,7 +84,7 @@ public Task Fetch(IRepository repo, UriString cloneUrl, params string[] refspecs } } - return Task.Factory.StartNew(() => + return Task.Run(() => { try { @@ -114,17 +94,15 @@ public Task Fetch(IRepository repo, UriString cloneUrl, params string[] refspecs var removeRemote = false; if (repo.Network.Remotes[remoteName] != null) { - // If a remote with this neme already exists, use a unique name and remove remote afterwards + // If a remote with this name already exists, use a unique name and remove remote afterwards remoteName = cloneUrl.Owner + "-" + Guid.NewGuid(); removeRemote = true; } - var remote = repo.Network.Remotes.Add(remoteName, remoteUri.ToString()); + repo.Network.Remotes.Add(remoteName, remoteUri.ToString()); try { -#pragma warning disable 0618 // TODO: Replace `Network.Fetch` with `Commands.Fetch`. - repo.Network.Fetch(remote, refspecs, fetchOptions); -#pragma warning restore 0618 + repo.Network.Fetch(remoteName, refspecs, fetchOptions); } finally { @@ -149,14 +127,11 @@ public Task Fetch(IRepository repository, string remoteName, params string[] ref Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); - return Task.Factory.StartNew(() => + return Task.Run(() => { try { - var remote = repository.Network.Remotes[remoteName]; -#pragma warning disable 0618 // TODO: Replace `Network.Fetch` with `Commands.Fetch`. - repository.Network.Fetch(remote, refspecs, fetchOptions); -#pragma warning restore 0618 + repository.Network.Fetch(remoteName, refspecs, fetchOptions); } catch (Exception ex) { @@ -173,112 +148,32 @@ public Task Checkout(IRepository repository, string branchName) Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(branchName, nameof(branchName)); - return Task.Factory.StartNew(() => + return Task.Run(() => { -#pragma warning disable 0618 // TODO: Replace `IRepository.Checkout` with `Commands.Checkout`. - repository.Checkout(branchName); -#pragma warning restore 0618 - }); - } - - public Task CreateBranch(IRepository repository, string branchName) - { - Guard.ArgumentNotNull(repository, nameof(repository)); - Guard.ArgumentNotEmptyString(branchName, nameof(branchName)); - - return Task.Factory.StartNew(() => - { - repository.CreateBranch(branchName); - }); - } - - public Task Compare( - IRepository repository, - string sha1, - string sha2, - bool detectRenames) - { - Guard.ArgumentNotNull(repository, nameof(repository)); - Guard.ArgumentNotEmptyString(sha1, nameof(sha1)); - Guard.ArgumentNotEmptyString(sha2, nameof(sha2)); - - return Task.Factory.StartNew(() => - { - var options = new CompareOptions - { - Similarity = detectRenames ? SimilarityOptions.Renames : SimilarityOptions.None - }; - - var commit1 = repository.Lookup(sha1); - var commit2 = repository.Lookup(sha2); - - if (commit1 != null && commit2 != null) + if (repository is Repository repo) { - return repository.Diff.Compare(commit1.Tree, commit2.Tree, options); + LibGit2Sharp.Commands.Checkout(repo, branchName); } else { - return null; + log.Error("Couldn't checkout because {Variable} isn't an instance of {Type}", nameof(repository), typeof(Repository)); } }); } - public Task Compare( - IRepository repository, - string sha1, - string sha2, - string path) + public async Task CommitExists(IRepository repository, string sha) { - Guard.ArgumentNotNull(repository, nameof(repository)); - Guard.ArgumentNotEmptyString(sha1, nameof(sha1)); - Guard.ArgumentNotEmptyString(sha2, nameof(sha2)); - Guard.ArgumentNotEmptyString(path, nameof(path)); - - return Task.Factory.StartNew(() => - { - var commit1 = repository.Lookup(sha1); - var commit2 = repository.Lookup(sha2); - - if (commit1 != null && commit2 != null) - { - return repository.Diff.Compare( - commit1.Tree, - commit2.Tree, - new[] { path }); - } - else - { - return null; - } - }); + return await Task.Run(() => repository.Lookup(sha) != null).ConfigureAwait(false); } - public Task CompareWith(IRepository repository, string sha1, string sha2, string path, byte[] contents) + public Task CreateBranch(IRepository repository, string branchName) { Guard.ArgumentNotNull(repository, nameof(repository)); - Guard.ArgumentNotEmptyString(sha1, nameof(sha1)); - Guard.ArgumentNotEmptyString(sha2, nameof(sha1)); - Guard.ArgumentNotEmptyString(path, nameof(path)); + Guard.ArgumentNotEmptyString(branchName, nameof(branchName)); - return Task.Factory.StartNew(() => + return Task.Run(() => { - var commit1 = repository.Lookup(sha1); - var commit2 = repository.Lookup(sha2); - - var treeChanges = repository.Diff.Compare(commit1.Tree, commit2.Tree); - var normalizedPath = path.Replace("/", "\\"); - var renamed = treeChanges.FirstOrDefault(x => x.Path == normalizedPath); - var oldPath = renamed?.OldPath ?? path; - - if (commit1 != null) - { - var contentStream = contents != null ? new MemoryStream(contents) : new MemoryStream(); - var blob1 = commit1[oldPath]?.Target as Blob ?? repository.ObjectDatabase.CreateBlob(new MemoryStream()); - var blob2 = repository.ObjectDatabase.CreateBlob(contentStream, path); - return repository.Diff.Compare(blob1, blob2); - } - - return null; + repository.CreateBranch(branchName); }); } @@ -287,7 +182,7 @@ public Task GetConfig(IRepository repository, string key) Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(key, nameof(key)); - return Task.Factory.StartNew(() => + return Task.Run(() => { var result = repository.Config.Get(key); return result != null ? result.Value : default(T); @@ -300,7 +195,7 @@ public Task SetConfig(IRepository repository, string key, string value) Guard.ArgumentNotEmptyString(key, nameof(key)); Guard.ArgumentNotEmptyString(value, nameof(value)); - return Task.Factory.StartNew(() => + return Task.Run(() => { repository.Config.Set(key, value); }); @@ -311,7 +206,7 @@ public Task SetRemote(IRepository repository, string remoteName, Uri url) Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); - return Task.Factory.StartNew(() => + return Task.Run(() => { repository.Config.Set("remote." + remoteName + ".url", url.ToString()); repository.Config.Set("remote." + remoteName + ".fetch", "+refs/heads/*:refs/remotes/" + remoteName + "/*"); @@ -324,7 +219,7 @@ public Task SetTrackingBranch(IRepository repository, string branchName, string Guard.ArgumentNotEmptyString(branchName, nameof(branchName)); Guard.ArgumentNotEmptyString(remoteName, nameof(remoteName)); - return Task.Factory.StartNew(() => + return Task.Run(() => { var remoteBranchName = IsCanonical(remoteName) ? remoteName : "refs/remotes/" + remoteName + "/" + branchName; var remoteBranch = repository.Branches[remoteBranchName]; @@ -342,7 +237,7 @@ public Task UnsetConfig(IRepository repository, string key) { Guard.ArgumentNotEmptyString(key, nameof(key)); - return Task.Factory.StartNew(() => + return Task.Run(() => { repository.Config.Unset(key); }); @@ -353,7 +248,7 @@ public Task GetHttpRemote(IRepository repo, string remote) Guard.ArgumentNotNull(repo, nameof(repo)); Guard.ArgumentNotEmptyString(remote, nameof(remote)); - return Task.Factory.StartNew(() => + return Task.Run(() => { var uri = gitService.GetRemoteUri(repo, remote); var remoteName = uri.IsHypertextTransferProtocol ? remote : remote + "-http"; @@ -364,40 +259,42 @@ public Task GetHttpRemote(IRepository repo, string remote) }); } - public Task ExtractFile(IRepository repository, string commitSha, string fileName) + public Task ExtractFile(IRepository repository, string commitSha, string relativePath) { Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(commitSha, nameof(commitSha)); - Guard.ArgumentNotEmptyString(fileName, nameof(fileName)); + Guard.ArgumentIsRelativePath(relativePath, nameof(relativePath)); - return Task.Factory.StartNew(() => + var gitPath = Paths.ToGitPath(relativePath); + return Task.Run(() => { var commit = repository.Lookup(commitSha); if (commit == null) { - throw new FileNotFoundException("Couldn't find '" + fileName + "' at commit " + commitSha + "."); + throw new FileNotFoundException("Couldn't find '" + gitPath + "' at commit " + commitSha + "."); } - var blob = commit[fileName]?.Target as Blob; + var blob = commit[gitPath]?.Target as Blob; return blob?.GetContentText(); }); } - public Task ExtractFileBinary(IRepository repository, string commitSha, string fileName) + public Task ExtractFileBinary(IRepository repository, string commitSha, string relativePath) { Guard.ArgumentNotNull(repository, nameof(repository)); Guard.ArgumentNotEmptyString(commitSha, nameof(commitSha)); - Guard.ArgumentNotEmptyString(fileName, nameof(fileName)); + Guard.ArgumentIsRelativePath(relativePath, nameof(relativePath)); - return Task.Factory.StartNew(() => + var gitPath = Paths.ToGitPath(relativePath); + return Task.Run(() => { var commit = repository.Lookup(commitSha); if (commit == null) { - throw new FileNotFoundException("Couldn't find '" + fileName + "' at commit " + commitSha + "."); + throw new FileNotFoundException("Couldn't find '" + gitPath + "' at commit " + commitSha + "."); } - var blob = commit[fileName]?.Target as Blob; + var blob = commit[gitPath]?.Target as Blob; if (blob != null) { @@ -413,16 +310,17 @@ public Task ExtractFileBinary(IRepository repository, string commitSha, }); } - public Task IsModified(IRepository repository, string path, byte[] contents) + public Task IsModified(IRepository repository, string relativePath, byte[] contents) { Guard.ArgumentNotNull(repository, nameof(repository)); - Guard.ArgumentNotEmptyString(path, nameof(path)); + Guard.ArgumentIsRelativePath(relativePath, nameof(relativePath)); - return Task.Factory.StartNew(() => + var gitPath = Paths.ToGitPath(relativePath); + return Task.Run(() => { - if (repository.RetrieveStatus(path) == FileStatus.Unaltered) + if (repository.RetrieveStatus(gitPath) == FileStatus.Unaltered) { - var treeEntry = repository.Head[path]; + var treeEntry = repository.Head[gitPath]; if (treeEntry?.TargetType != TreeEntryTargetType.Blob) { return false; @@ -431,7 +329,7 @@ public Task IsModified(IRepository repository, string path, byte[] content var blob1 = (Blob)treeEntry.Target; using (var s = contents != null ? new MemoryStream(contents) : new MemoryStream()) { - var blob2 = repository.ObjectDatabase.CreateBlob(s, path); + var blob2 = repository.ObjectDatabase.CreateBlob(s, gitPath); var diff = repository.Diff.Compare(blob1, blob2); return diff.LinesAdded != 0 || diff.LinesDeleted != 0; } @@ -486,7 +384,7 @@ public Task IsHeadPushed(IRepository repo) { Guard.ArgumentNotNull(repo, nameof(repo)); - return Task.Factory.StartNew(() => + return Task.Run(() => { return repo.Head.TrackingDetails.AheadBy == 0; }); @@ -498,7 +396,7 @@ public Task> GetMessagesForUniqueCommits( string compareBranch, int maxCommits) { - return Task.Factory.StartNew(() => + return Task.Run(() => { var baseCommit = repo.Lookup(baseBranch); var compareCommit = repo.Lookup(compareBranch); diff --git a/src/GitHub.App/Services/GitHubContextService.cs b/src/GitHub.App/Services/GitHubContextService.cs index 640e85a659..5d8944bbb4 100644 --- a/src/GitHub.App/Services/GitHubContextService.cs +++ b/src/GitHub.App/Services/GitHubContextService.cs @@ -3,6 +3,7 @@ using System.Text; using System.Linq; using System.Windows; +using System.Globalization; using System.Threading.Tasks; using System.Collections.Generic; using System.ComponentModel.Composition; @@ -25,6 +26,7 @@ public class GitHubContextService : IGitHubContextService { readonly IGitHubServiceProvider serviceProvider; readonly IGitService gitService; + readonly IVSServices vsServices; readonly Lazy textManager; // USERID_REGEX = /[a-z0-9][a-z0-9\-\_]*/i @@ -61,13 +63,38 @@ public class GitHubContextService : IGitHubContextService static readonly Regex tempFileObjectishRegex = new Regex(@"\\TFSTemp\\[^\\]*[.](?[a-z0-9]{8})[.][^.\\]*$", RegexOptions.Compiled); [ImportingConstructor] - public GitHubContextService(IGitHubServiceProvider serviceProvider, IGitService gitService) + public GitHubContextService(IGitHubServiceProvider serviceProvider, IGitService gitService, IVSServices vsServices) { this.serviceProvider = serviceProvider; this.gitService = gitService; + this.vsServices = vsServices; textManager = new Lazy(() => serviceProvider.GetService()); } + /// + public void TryNavigateToContext(string repositoryDir, GitHubContext context) + { + if (context?.LinkType == LinkType.Blob) + { + var (commitish, path, commitSha) = ResolveBlob(repositoryDir, context); + if (commitish == null && path == null) + { + var message = string.Format(CultureInfo.CurrentCulture, Resources.CouldntFindCorrespondingFile, context.Url); + vsServices.ShowMessageBoxInfo(message); + return; + } + + var hasChanges = HasChangesInWorkingDirectory(repositoryDir, commitish, path); + if (hasChanges) + { + var message = string.Format(CultureInfo.CurrentCulture, Resources.ChangesInWorkingDirectoryMessage, commitish); + vsServices.ShowMessageBoxInfo(message); + } + + TryOpenFile(repositoryDir, context); + } + } + /// public GitHubContext FindContextFromClipboard() { @@ -97,7 +124,27 @@ public GitHubContext FindContextFromUrl(string url) Url = uri }; - var repositoryPrefix = uri.ToRepositoryUrl().ToString() + "/"; + if (uri.Owner == null) + { + context.LinkType = LinkType.Unknown; + return context; + } + + if (uri.RepositoryName == null) + { + context.LinkType = LinkType.Unknown; + return context; + } + + var repositoryUrl = uri.ToRepositoryUrl().ToString(); + if (string.Equals(url, repositoryUrl, StringComparison.OrdinalIgnoreCase) || + string.Equals(url, repositoryUrl + ".git", StringComparison.OrdinalIgnoreCase)) + { + context.LinkType = LinkType.Repository; + return context; + } + + var repositoryPrefix = repositoryUrl + "/"; if (!url.StartsWith(repositoryPrefix, StringComparison.OrdinalIgnoreCase)) { return context; @@ -235,7 +282,8 @@ public bool TryOpenFile(string repositoryDir, GitHubContext context) return false; } - var fullPath = Path.Combine(repositoryDir, path.Replace('/', '\\')); + var relativePath = Paths.ToWindowsPath(path); + var fullPath = Path.Combine(repositoryDir, relativePath); var textView = OpenDocument(fullPath); SetSelection(textView, context); return true; @@ -357,12 +405,17 @@ public string FindObjectishForTFSTempFile(string tempFile) } /// - public bool HasChangesInWorkingDirectory(string repositoryDir, string commitish, string path) + public bool HasChangesInWorkingDirectory(string repositoryDir, string commitish, string relativePath) { + Guard.ArgumentNotNull(repositoryDir, nameof(repositoryDir)); + Guard.ArgumentNotNull(commitish, nameof(commitish)); + Guard.ArgumentIsRelativePath(relativePath, nameof(relativePath)); + + var gitPath = Paths.ToGitPath(relativePath); using (var repo = gitService.GetRepository(repositoryDir)) { var commit = repo.Lookup(commitish); - var paths = new[] { path }; + var paths = new[] { gitPath }; return repo.Diff.Compare(commit.Tree, DiffTargets.WorkingDirectory, paths).Count() > 0; } diff --git a/src/GitHub.App/Services/GitHubCredentialProvider.cs b/src/GitHub.App/Services/GitHubCredentialProvider.cs index edec581c30..0f6459aaca 100644 --- a/src/GitHub.App/Services/GitHubCredentialProvider.cs +++ b/src/GitHub.App/Services/GitHubCredentialProvider.cs @@ -6,6 +6,7 @@ using GitHub.Primitives; using LibGit2Sharp; using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; using Serilog; namespace GitHub.Services @@ -18,11 +19,12 @@ class GitHubCredentialProvider : IGitHubCredentialProvider readonly IKeychain keychain; [ImportingConstructor] - public GitHubCredentialProvider(IKeychain keychain) + public GitHubCredentialProvider(IKeychain keychain, [Import(AllowDefault = true)] JoinableTaskContext joinableTaskContext) { Guard.ArgumentNotNull(keychain, nameof(keychain)); this.keychain = keychain; + JoinableTaskContext = joinableTaskContext ?? ThreadHelper.JoinableTaskContext; } /// @@ -38,7 +40,7 @@ public Credentials HandleCredentials(string url, string username, SupportedCrede try { - var credentials = ThreadHelper.JoinableTaskFactory.Run(async () => await keychain.Load(host)); + var credentials = JoinableTaskContext.Factory.Run(async () => await keychain.Load(host)); return new UsernamePasswordCredentials { Username = credentials.Item1, @@ -51,5 +53,7 @@ public Credentials HandleCredentials(string url, string username, SupportedCrede return null; } } + + JoinableTaskContext JoinableTaskContext { get; } } } \ No newline at end of file diff --git a/src/GitHub.App/Services/IAutoCompleteSource.cs b/src/GitHub.App/Services/IAutoCompleteSource.cs new file mode 100644 index 0000000000..09b77c4cc3 --- /dev/null +++ b/src/GitHub.App/Services/IAutoCompleteSource.cs @@ -0,0 +1,13 @@ +using System; +using GitHub.Models; + +namespace GitHub.Services +{ + public interface IAutoCompleteSource + { + IObservable GetSuggestions(); + + // The prefix used to trigger auto completion. + string Prefix { get; } + } +} diff --git a/src/GitHub.App/Services/IssueishService.cs b/src/GitHub.App/Services/IssueishService.cs new file mode 100644 index 0000000000..481d9f7f3c --- /dev/null +++ b/src/GitHub.App/Services/IssueishService.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using GitHub.Api; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Primitives; +using Octokit; +using Octokit.GraphQL; +using Octokit.GraphQL.Model; +using static Octokit.GraphQL.Variable; + +namespace GitHub.Services +{ + /// + /// Base class for issue and pull request services. + /// + public abstract class IssueishService : IIssueishService + { + static ICompiledQuery postComment; + readonly IApiClientFactory apiClientFactory; + readonly IGraphQLClientFactory graphqlFactory; + + /// + /// Initializes a new instance of the class. + /// + /// The API client factory. + /// The GraphQL client factory. + public IssueishService( + IApiClientFactory apiClientFactory, + IGraphQLClientFactory graphqlFactory) + { + this.apiClientFactory = apiClientFactory; + this.graphqlFactory = graphqlFactory; + } + + /// + public async Task CloseIssueish(HostAddress address, string owner, string repository, int number) + { + var client = await apiClientFactory.CreateGitHubClient(address).ConfigureAwait(false); + var update = new IssueUpdate { State = ItemState.Closed }; + await client.Issue.Update(owner, repository, number, update).ConfigureAwait(false); + } + + /// + public async Task ReopenIssueish(HostAddress address, string owner, string repository, int number) + { + var client = await apiClientFactory.CreateGitHubClient(address).ConfigureAwait(false); + var update = new IssueUpdate { State = ItemState.Open }; + await client.Issue.Update(owner, repository, number, update).ConfigureAwait(false); + } + + /// + public async Task PostComment(HostAddress address, string issueishId, string body) + { + var input = new AddCommentInput + { + Body = body, + SubjectId = new ID(issueishId), + }; + + if (postComment == null) + { + postComment = new Mutation() + .AddComment(Var(nameof(input))) + .CommentEdge + .Node + .Select(comment => new CommentModel + { + Author = new ActorModel + { + Login = comment.Author.Login, + AvatarUrl = comment.Author.AvatarUrl(null), + }, + Body = comment.Body, + CreatedAt = comment.CreatedAt, + DatabaseId = comment.DatabaseId.Value, + Id = comment.Id.Value, + Url = comment.Url, + }).Compile(); + } + + var vars = new Dictionary + { + { nameof(input), input }, + }; + + var graphql = await graphqlFactory.CreateConnection(address).ConfigureAwait(false); + return await graphql.Run(postComment, vars).ConfigureAwait(false); + } + + public async Task DeleteComment( + HostAddress address, + string owner, + string repository, + int commentId) + { + var client = await apiClientFactory.CreateGitHubClient(address).ConfigureAwait(false); + await client.Issue.Comment.Delete(owner, repository, commentId).ConfigureAwait(false); + } + + public async Task EditComment( + HostAddress address, + string owner, + string repository, + int commentId, + string body) + { + var client = await apiClientFactory.CreateGitHubClient(address).ConfigureAwait(false); + await client.Issue.Comment.Update(owner, repository, commentId, body).ConfigureAwait(false); + } + } +} diff --git a/src/GitHub.App/Services/IssuesAutoCompleteSource.cs b/src/GitHub.App/Services/IssuesAutoCompleteSource.cs new file mode 100644 index 0000000000..d2c64e671c --- /dev/null +++ b/src/GitHub.App/Services/IssuesAutoCompleteSource.cs @@ -0,0 +1,136 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Primitives; +using Octokit.GraphQL; +using Octokit.GraphQL.Model; +using static Octokit.GraphQL.Variable; + +namespace GitHub.Services +{ + [Export(typeof(IAutoCompleteSource))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class IssuesAutoCompleteSource : IAutoCompleteSource + { + readonly ITeamExplorerContext teamExplorerContext; + readonly IGraphQLClientFactory graphqlFactory; + ICompiledQuery> query; + + [ImportingConstructor] + public IssuesAutoCompleteSource(ITeamExplorerContext teamExplorerContext, IGraphQLClientFactory graphqlFactory) + { + Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext)); + Guard.ArgumentNotNull(graphqlFactory, nameof(graphqlFactory)); + + this.teamExplorerContext = teamExplorerContext; + this.graphqlFactory = graphqlFactory; + } + + public IObservable GetSuggestions() + { + var localRepositoryModel = teamExplorerContext.ActiveRepository; + + var hostAddress = HostAddress.Create(localRepositoryModel.CloneUrl.Host); + var owner = localRepositoryModel.Owner; + var name = localRepositoryModel.Name; + + string filter; + string after; + + if (query == null) + { + query = new Query().Search(query: Var(nameof(filter)), SearchType.Issue, 100, after: Var(nameof(after))) + .Select(item => new Page + { + Items = item.Nodes.Select(searchResultItem => + searchResultItem.Switch(selector => selector + .Issue(i => new SuggestionItem("#" + i.Number, i.Title) { LastModifiedDate = i.LastEditedAt }) + .PullRequest(p => new SuggestionItem("#" + p.Number, p.Title) { LastModifiedDate = p.LastEditedAt })) + ).ToList(), + EndCursor = item.PageInfo.EndCursor, + HasNextPage = item.PageInfo.HasNextPage, + TotalCount = item.IssueCount + }) + .Compile(); + } + + filter = $"repo:{owner}/{name}"; + + return Observable.FromAsync(async () => + { + var results = new List(); + + var variables = new Dictionary + { + {nameof(filter), filter }, + }; + + var connection = await graphqlFactory.CreateConnection(hostAddress); + var searchResults = await connection.Run(query, variables); + + results.AddRange(searchResults.Items); + + while (searchResults.HasNextPage) + { + variables[nameof(after)] = searchResults.EndCursor; + searchResults = await connection.Run(query, variables); + + results.AddRange(searchResults.Items); + } + + return results.Select(item => new IssueAutoCompleteSuggestion(item, Prefix)); + + }).SelectMany(observable => observable); + } + + class SearchResult + { + public SuggestionItem SuggestionItem { get; set; } + } + + public string Prefix + { + get { return "#"; } + } + + class IssueAutoCompleteSuggestion : AutoCompleteSuggestion + { + // Just needs to be some value before GitHub stored its first issue. + static readonly DateTimeOffset lowerBound = new DateTimeOffset(2000, 1, 1, 12, 0, 0, TimeSpan.FromSeconds(0)); + + readonly SuggestionItem suggestion; + public IssueAutoCompleteSuggestion(SuggestionItem suggestion, string prefix) + : base(suggestion.Name, suggestion.Description, prefix) + { + this.suggestion = suggestion; + } + + public override int GetSortRank(string text) + { + // We need to override the sort rank behavior because when we display issues, we include the prefix + // unlike mentions. So we need to account for that in how we do filtering. + if (text.Length == 0) + { + return (int) ((suggestion.LastModifiedDate ?? lowerBound) - lowerBound).TotalSeconds; + } + // Name is always "#" followed by issue number. + return Name.StartsWith("#" + text, StringComparison.OrdinalIgnoreCase) + ? 1 + : DescriptionWords.Any(word => word.StartsWith(text, StringComparison.OrdinalIgnoreCase)) + ? 0 + : -1; + } + + // This is what gets "completed" when you tab. + public override string ToString() + { + return Name; + } + } + } +} diff --git a/src/GitHub.App/Services/MentionsAutoCompleteSource.cs b/src/GitHub.App/Services/MentionsAutoCompleteSource.cs new file mode 100644 index 0000000000..dab165ce89 --- /dev/null +++ b/src/GitHub.App/Services/MentionsAutoCompleteSource.cs @@ -0,0 +1,96 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive.Linq; +using System.Windows.Media.Imaging; +using GitHub.Api; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Primitives; +using Octokit.GraphQL; +using static Octokit.GraphQL.Variable; + +namespace GitHub.Services +{ + /// + /// Supplies @mentions auto complete suggestions. + /// + [Export(typeof(IAutoCompleteSource))] + [PartCreationPolicy(CreationPolicy.Shared)] + public class MentionsAutoCompleteSource : IAutoCompleteSource + { + const string DefaultAvatar = "pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png"; + + readonly ITeamExplorerContext teamExplorerContext; + readonly IGraphQLClientFactory graphqlFactory; + readonly IAvatarProvider avatarProvider; + ICompiledQuery> query; + + [ImportingConstructor] + public MentionsAutoCompleteSource( + ITeamExplorerContext teamExplorerContext, + IGraphQLClientFactory graphqlFactory, + IAvatarProvider avatarProvider) + { + Guard.ArgumentNotNull(teamExplorerContext, nameof(teamExplorerContext)); + Guard.ArgumentNotNull(graphqlFactory, nameof(graphqlFactory)); + Guard.ArgumentNotNull(avatarProvider, nameof(avatarProvider)); + + this.teamExplorerContext = teamExplorerContext; + this.graphqlFactory = graphqlFactory; + this.avatarProvider = avatarProvider; + } + + public IObservable GetSuggestions() + { + var localRepositoryModel = teamExplorerContext.ActiveRepository; + + var hostAddress = HostAddress.Create(localRepositoryModel.CloneUrl.Host); + var owner = localRepositoryModel.Owner; + var name = localRepositoryModel.Name; + + if (query == null) + { + query = new Query().Repository(owner: Var(nameof(owner)), name: Var(nameof(name))) + .Select(repository => + repository.MentionableUsers(null, null, null, null, null) + .AllPages() + .Select(sourceItem => + new SuggestionItem(sourceItem.Login, + sourceItem.Name ?? "(unknown)", + sourceItem.AvatarUrl(null))) + .ToList()) + .Compile(); + } + + var variables = new Dictionary + { + {nameof(owner), owner }, + {nameof(name), name }, + }; + + return Observable.FromAsync(async () => + { + var connection = await graphqlFactory.CreateConnection(hostAddress); + var suggestions = await connection.Run(query, variables); + return suggestions.Select(suggestion => new AutoCompleteSuggestion(suggestion.Name, + suggestion.Description, + ResolveImage(suggestion), + Prefix)); + }).SelectMany(enumerable => enumerable); + } + + IObservable ResolveImage(SuggestionItem uri) + { + if (uri.ImageUrl != null) + { + return avatarProvider.GetAvatar(uri.ImageUrl); + } + + return Observable.Return(AvatarProvider.CreateBitmapImage(DefaultAvatar)); + } + + public string Prefix => "@"; + } +} diff --git a/src/GitHub.App/Services/ModelService.cs b/src/GitHub.App/Services/ModelService.cs index 9693662e30..0a9f7e03a6 100644 --- a/src/GitHub.App/Services/ModelService.cs +++ b/src/GitHub.App/Services/ModelService.cs @@ -22,6 +22,8 @@ using Serilog; using static Octokit.GraphQL.Variable; +#pragma warning disable CA1034 // Nested types should not be visible + namespace GitHub.Services { [Export(typeof(IModelService))] @@ -365,7 +367,7 @@ RemoteRepositoryModel Create(RepositoryCacheItem item) }; } - GitReferenceModel Create(GitReferenceCacheItem item) + static GitReferenceModel Create(GitReferenceCacheItem item) { return new GitReferenceModel(item.Ref, item.Label, item.Sha, item.RepositoryCloneUrl); } @@ -388,7 +390,7 @@ IPullRequestModel Create(PullRequestCacheItem prCacheItem) Head = Create(prCacheItem.Head), State = prCacheItem.State.HasValue ? prCacheItem.State.Value : - prCacheItem.IsOpen.Value ? PullRequestStateEnum.Open : PullRequestStateEnum.Closed, + prCacheItem.IsOpen.Value ? PullRequestState.Open : PullRequestState.Closed, }; } @@ -522,25 +524,25 @@ public PullRequestCacheItem(PullRequest pr) public string Body { get; set; } // Nullable for compatibility with old caches. - public PullRequestStateEnum? State { get; set; } + public PullRequestState? State { get; set; } // This fields exists only for compatibility with old caches. The State property should be used. public bool? IsOpen { get; set; } public bool? Merged { get; set; } - static PullRequestStateEnum GetState(PullRequest pullRequest) + static PullRequestState GetState(PullRequest pullRequest) { if (pullRequest.State == ItemState.Open) { - return PullRequestStateEnum.Open; + return PullRequestState.Open; } else if (pullRequest.Merged) { - return PullRequestStateEnum.Merged; + return PullRequestState.Merged; } else { - return PullRequestStateEnum.Closed; + return PullRequestState.Closed; } } } diff --git a/src/GitHub.App/Services/OAuthCallbackListener.cs b/src/GitHub.App/Services/OAuthCallbackListener.cs index e1a48307e4..34cd3cde23 100644 --- a/src/GitHub.App/Services/OAuthCallbackListener.cs +++ b/src/GitHub.App/Services/OAuthCallbackListener.cs @@ -1,4 +1,6 @@ -using System.ComponentModel.Composition; +using System; +using System.ComponentModel.Composition; +using System.Diagnostics; using System.Net; using System.Threading; using System.Threading.Tasks; @@ -39,6 +41,7 @@ public OAuthCallbackListener(IHttpListener httpListener) } public readonly static string CallbackUrl = Invariant($"http://localhost:{CallbackPort}/"); + private IHttpListenerContext lastContext; public async Task Listen(string id, CancellationToken cancel) { @@ -51,22 +54,29 @@ public async Task Listen(string id, CancellationToken cancel) { while (true) { - var context = await httpListener.GetContextAsync().ConfigureAwait(false); - var foo = context.Request; - var queryParts = HttpUtility.ParseQueryString(context.Request.Url.Query); + lastContext = await httpListener.GetContextAsync().ConfigureAwait(false); + var queryParts = HttpUtility.ParseQueryString(lastContext.Request.Url.Query); if (queryParts["state"] == id) { - context.Response.Close(); return queryParts["code"]; } } } } - finally + catch(Exception) { httpListener.Stop(); + throw; } } + + public void RedirectLastContext(Uri url) + { + lastContext.Response.Redirect(url); + lastContext.Response.Close(); + + httpListener.Stop(); + } } } diff --git a/src/GitHub.App/Services/PullRequestEditorService.cs b/src/GitHub.App/Services/PullRequestEditorService.cs index aaf44c50ad..50bf091988 100644 --- a/src/GitHub.App/Services/PullRequestEditorService.cs +++ b/src/GitHub.App/Services/PullRequestEditorService.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using EnvDTE; using GitHub.Commands; +using GitHub.Primitives; using GitHub.Extensions; using GitHub.Models; using GitHub.Models.Drafts; @@ -89,13 +90,12 @@ public async Task OpenFile( try { - var fullPath = GetAbsolutePath(session.LocalRepository, relativePath); string fileName; string commitSha; if (workingDirectory) { - fileName = fullPath; + fileName = Path.Combine(session.LocalRepository.LocalPath, relativePath); commitSha = null; } else @@ -119,7 +119,7 @@ public async Task OpenFile( if (!workingDirectory) { - AddBufferTag(wpfTextView.TextBuffer, session, fullPath, commitSha, null); + AddBufferTag(wpfTextView.TextBuffer, session, relativePath, commitSha, null); EnableNavigateToEditor(textView, session); } } @@ -219,10 +219,10 @@ await pullRequestService.ExtractToTempFile( frame = VisualStudio.Services.DifferenceService.OpenComparisonWindow2( leftFile, rightFile, - caption, - tooltip, - leftLabel, - rightLabel, + SanitizeForDisplay(caption), + SanitizeForDisplay(tooltip), + SanitizeForDisplay(leftLabel), + SanitizeForDisplay(rightLabel), string.Empty, string.Empty, (uint)options); @@ -232,12 +232,12 @@ await pullRequestService.ExtractToTempFile( var leftText = diffViewer.LeftView.TextBuffer.CurrentSnapshot.GetText(); var rightText = diffViewer.RightView.TextBuffer.CurrentSnapshot.GetText(); - if (leftText == string.Empty) + if (leftText.Length == 0) { // Don't show LeftView when empty. diffViewer.ViewMode = DifferenceViewMode.RightViewOnly; } - else if (rightText == string.Empty) + else if (rightText.Length == 0) { // Don't show RightView when empty. diffViewer.ViewMode = DifferenceViewMode.LeftViewOnly; @@ -284,8 +284,15 @@ await pullRequestService.ExtractToTempFile( } } + private static string SanitizeForDisplay(string caption) + { + // The diff window passes captions and tooltips through string.Format, with {0} and {1} being the left and right file respectively, but we already + // nicely format the file names with extra info we know, so we have to escape braces to prevent unwanted formatting, or invalid format errors. + return caption.Replace("{", "{{").Replace("}", "}}"); + } + /// - public async Task OpenDiff( + public Task OpenDiff( IPullRequestSession session, string relativePath, IInlineCommentThreadModel thread) @@ -294,11 +301,17 @@ public async Task OpenDiff( Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); Guard.ArgumentNotNull(thread, nameof(thread)); - var diffViewer = await OpenDiff(session, relativePath, thread.CommitSha, scrollToFirstDraftOrDiff: false); + return OpenDiff(session, relativePath, thread.CommitSha, thread.LineNumber - 1); + } - var param = (object)new InlineCommentNavigationParams + /// + public async Task OpenDiff(IPullRequestSession session, string relativePath, string headSha, int nextInlineTagFromLine) + { + var diffViewer = await OpenDiff(session, relativePath, headSha, scrollToFirstDraftOrDiff: false); + + var param = (object) new InlineCommentNavigationParams { - FromLine = thread.LineNumber - 1, + FromLine = nextInlineTagFromLine, }; // HACK: We need to wait here for the inline comment tags to initialize so we can find the next inline comment. @@ -430,7 +443,7 @@ public int FindMatchingLine(IList fromLines, IList toLines, int /// The 0-based line we're navigating from. /// The number of similar matched lines in /// Find the nearest matching line in . - public int FindNearestMatchingLine(IList fromLines, IList toLines, int line, out int matchedLines) + public static int FindNearestMatchingLine(IList fromLines, IList toLines, int line, out int matchedLines) { line = line < fromLines.Count ? line : fromLines.Count - 1; // VS shows one extra line at end var fromLine = fromLines[line]; @@ -472,13 +485,6 @@ public int FindNearestMatchingLine(IList fromLines, IList toLine return matchingLine; } - static string GetAbsolutePath(LocalRepositoryModel localRepository, string relativePath) - { - var localPath = localRepository.LocalPath; - relativePath = relativePath.Replace('/', Path.DirectorySeparatorChar); - return Path.Combine(localPath, relativePath); - } - string GetText(IVsTextView textView) { IVsTextLines buffer; @@ -555,13 +561,13 @@ void ShowErrorInStatusBar(string message, Exception e) void AddBufferTag( ITextBuffer buffer, IPullRequestSession session, - string path, + string relativePath, string commitSha, DiffSide? side) { buffer.Properties.GetOrCreateSingletonProperty( typeof(PullRequestTextBufferInfo), - () => new PullRequestTextBufferInfo(session, path, commitSha, side)); + () => new PullRequestTextBufferInfo(session, relativePath, commitSha, side)); var projection = buffer as IProjectionBuffer; @@ -569,7 +575,7 @@ void AddBufferTag( { foreach (var source in projection.SourceBuffers) { - AddBufferTag(source, session, path, commitSha, side); + AddBufferTag(source, session, relativePath, commitSha, side); } } } @@ -636,9 +642,10 @@ async Task GetBaseFileName(IPullRequestSession session, IPullRequestSess session.LocalRepository, session.PullRequest)) { - var fileChange = changes.FirstOrDefault(x => x.Path == file.RelativePath); + var gitPath = Paths.ToGitPath(file.RelativePath); + var fileChange = changes.FirstOrDefault(x => x.Path == gitPath); return fileChange?.Status == LibGit2Sharp.ChangeKind.Renamed ? - fileChange.OldPath : file.RelativePath; + Paths.ToWindowsPath(fileChange.OldPath) : file.RelativePath; } } diff --git a/src/GitHub.App/Services/PullRequestService.cs b/src/GitHub.App/Services/PullRequestService.cs index 9e141857fa..54f98852eb 100644 --- a/src/GitHub.App/Services/PullRequestService.cs +++ b/src/GitHub.App/Services/PullRequestService.cs @@ -8,17 +8,21 @@ using System.Reactive; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; +using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; using GitHub.Api; using GitHub.App.Services; using GitHub.Extensions; +using GitHub.Factories; using GitHub.Logging; using GitHub.Models; using GitHub.Primitives; using LibGit2Sharp; +using Microsoft.VisualStudio.StaticReviews.Contracts; using Octokit.GraphQL; using Octokit.GraphQL.Model; using Rothko; @@ -32,7 +36,7 @@ namespace GitHub.Services { [Export(typeof(IPullRequestService))] [PartCreationPolicy(CreationPolicy.Shared)] - public class PullRequestService : IPullRequestService + public class PullRequestService : IssueishService, IPullRequestService, IStaticReviewFileMap { const string SettingCreatedByGHfVS = "created-by-ghfvs"; const string SettingGHfVSPullRequest = "ghfvs-pr-owner-number"; @@ -58,14 +62,18 @@ public class PullRequestService : IPullRequestService readonly IOperatingSystem os; readonly IUsageTracker usageTracker; + readonly IDictionary tempFileMappings; + [ImportingConstructor] public PullRequestService( IGitClient gitClient, IGitService gitService, IVSGitExt gitExt, + IApiClientFactory apiClientFactory, IGraphQLClientFactory graphqlFactory, IOperatingSystem os, IUsageTracker usageTracker) + : base(apiClientFactory, graphqlFactory) { this.gitClient = gitClient; this.gitService = gitService; @@ -73,6 +81,7 @@ public PullRequestService( this.graphqlFactory = graphqlFactory; this.os = os; this.usageTracker = usageTracker; + this.tempFileMappings = new Dictionary(StringComparer.OrdinalIgnoreCase); } public async Task> ReadPullRequests( @@ -80,7 +89,7 @@ public async Task> ReadPullRequests( string owner, string name, string after, - PullRequestStateEnum[] states) + Models.PullRequestState[] states) { ICompiledQuery> query; @@ -90,7 +99,7 @@ public async Task> ReadPullRequests( if (readPullRequests == null) { readPullRequests = new Query() - .Repository(Var(nameof(owner)), Var(nameof(name))) + .Repository(owner: Var(nameof(owner)), name: Var(nameof(name))) .PullRequests( first: 100, after: Var(nameof(after)), @@ -115,7 +124,7 @@ public async Task> ReadPullRequests( { Conclusion = run.Conclusion.FromGraphQl(), Status = run.Status.FromGraphQl() - }).ToList() + }).ToList(), }).ToList(), Statuses = commit.Commit.Status .Select(context => @@ -151,7 +160,7 @@ public async Task> ReadPullRequests( if (readPullRequestsEnterprise == null) { readPullRequestsEnterprise = new Query() - .Repository(Var(nameof(owner)), Var(nameof(name))) + .Repository(owner: Var(nameof(owner)), name: Var(nameof(name))) .PullRequests( first: 100, after: Var(nameof(after)), @@ -168,12 +177,14 @@ public async Task> ReadPullRequests( LastCommit = pr.Commits(null, null, 1, null).Nodes.Select(commit => new LastCommitSummaryAdapter { - Statuses = commit.Commit.Status - .Select(context => - context.Contexts.Select(statusContext => new StatusSummaryModel - { - State = statusContext.State.FromGraphQl(), - }).ToList() + Statuses = commit.Commit.Status.Select(context => + context == null + ? null + : context.Contexts + .Select(statusContext => new StatusSummaryModel + { + State = statusContext.State.FromGraphQl() + }).ToList() ).SingleOrDefault() }).ToList().FirstOrDefault(), Author = new ActorModel @@ -204,10 +215,11 @@ public async Task> ReadPullRequests( { nameof(owner), owner }, { nameof(name), name }, { nameof(after), after }, - { nameof(states), states.Select(x => (PullRequestState)x).ToList() }, + { nameof(states), states.Select(x => (Octokit.GraphQL.Model.PullRequestState)x).ToList() }, }; - var result = await graphql.Run(query, vars); + var region = owner + '/' + name + "/pr-list"; + var result = await graphql.Run(query, vars, regionName: region); foreach (var item in result.Items.Cast()) { @@ -215,64 +227,65 @@ public async Task> ReadPullRequests( item.Reviews = null; var checkRuns = item.LastCommit?.CheckSuites?.SelectMany(model => model.CheckRuns).ToArray(); + var statuses = item.LastCommit?.Statuses; - var hasCheckRuns = checkRuns?.Any() ?? false; - var hasStatuses = item.LastCommit?.Statuses?.Any() ?? false; + var totalCount = 0; + var pendingCount = 0; + var successCount = 0; + var errorCount = 0; - if (!hasCheckRuns && !hasStatuses) + if (checkRuns != null) { - item.Checks = PullRequestChecksState.None; + totalCount += checkRuns.Length; + + pendingCount += checkRuns.Count(model => model.Status != CheckStatusState.Completed); + + successCount += checkRuns.Count(model => model.Status == CheckStatusState.Completed && + model.Conclusion.HasValue && + (model.Conclusion == CheckConclusionState.Success || + model.Conclusion == CheckConclusionState.Neutral)); + errorCount += checkRuns.Count(model => model.Status == CheckStatusState.Completed && + model.Conclusion.HasValue && + !(model.Conclusion == CheckConclusionState.Success || + model.Conclusion == CheckConclusionState.Neutral)); } - else + + if (statuses != null) { - var checksHasFailure = false; - var checksHasCompleteSuccess = true; + totalCount += statuses.Count; - if (hasCheckRuns) - { - checksHasFailure = checkRuns - .Any(model => model.Conclusion.HasValue - && (model.Conclusion.Value == CheckConclusionState.Failure - || model.Conclusion.Value == CheckConclusionState.ActionRequired)); + pendingCount += statuses.Count(model => + model.State == StatusState.Pending || model.State == StatusState.Expected); - if (!checksHasFailure) - { - checksHasCompleteSuccess = checkRuns - .All(model => model.Conclusion.HasValue - && (model.Conclusion.Value == CheckConclusionState.Success - || model.Conclusion.Value == CheckConclusionState.Neutral)); - } - } + successCount += statuses.Count(model => model.State == StatusState.Success); - var statusHasFailure = false; - var statusHasCompleteSuccess = true; + errorCount += statuses.Count(model => + model.State == StatusState.Error || model.State == StatusState.Failure); + } - if (!checksHasFailure && hasStatuses) - { - statusHasFailure = item.LastCommit - .Statuses - .Any(status => status.State == StatusState.Failure - || status.State == StatusState.Error); + item.ChecksPendingCount = pendingCount; + item.ChecksSuccessCount = successCount; + item.ChecksErrorCount = errorCount; - if (!statusHasFailure) - { - statusHasCompleteSuccess = - item.LastCommit.Statuses.All(status => status.State == StatusState.Success); - } - } - - if (checksHasFailure || statusHasFailure) - { - item.Checks = PullRequestChecksState.Failure; - } - else if (statusHasCompleteSuccess && checksHasCompleteSuccess) - { - item.Checks = PullRequestChecksState.Success; - } - else - { - item.Checks = PullRequestChecksState.Pending; - } + if (totalCount == 0) + { + item.ChecksSummary = PullRequestChecksSummaryState.None; + } + else if (totalCount == pendingCount) + { + item.ChecksSummary = PullRequestChecksSummaryState.Pending; + } + else if (totalCount == successCount) + { + item.ChecksSummary = PullRequestChecksSummaryState.Success; + } + else if (totalCount == errorCount) + { + item.ChecksSummary = PullRequestChecksSummaryState.Failure; + } + else + { + item.ChecksSummary = PullRequestChecksSummaryState.Mixed; } item.LastCommit = null; @@ -281,6 +294,14 @@ public async Task> ReadPullRequests( return result; } + public async Task ClearPullRequestsCache(HostAddress address, string owner, string name) + { + var region = owner + '/' + name + "/pr-list"; + var graphql = await graphqlFactory.CreateConnection(address); + + await graphql.ClearCache(region); + } + public async Task> ReadAssignableUsers( HostAddress address, string owner, @@ -290,7 +311,7 @@ public async Task> ReadAssignableUsers( if (readAssignableUsers == null) { readAssignableUsers = new Query() - .Repository(Var(nameof(owner)), Var(nameof(name))) + .Repository(owner: Var(nameof(owner)), name: Var(nameof(name))) .AssignableUsers(first: 100, after: Var(nameof(after))) .Select(connection => new Page { @@ -313,7 +334,7 @@ public async Task> ReadAssignableUsers( { nameof(after), after }, }; - return await graphql.Run(readAssignableUsers, vars); + return await graphql.Run(readAssignableUsers, vars, cacheDuration: TimeSpan.FromHours(1)); } public IObservable CreatePullRequest(IModelService modelService, @@ -567,6 +588,21 @@ public IObservable Checkout(LocalRepositoryModel repository, PullRequestDe }); } + public async Task FetchCommit(LocalRepositoryModel localRepository, RepositoryModel remoteRepository, string sha) + { + using (var repo = gitService.GetRepository(localRepository.LocalPath)) + { + if (!await gitClient.CommitExists(repo, sha).ConfigureAwait(false)) + { + var remote = await CreateRemote(repo, remoteRepository.CloneUrl).ConfigureAwait(false); + await gitClient.Fetch(repo, remote).ConfigureAwait(false); + return await gitClient.CommitExists(repo, sha).ConfigureAwait(false); + } + + return true; + } + } + public IObservable GetDefaultLocalBranchName(LocalRepositoryModel repository, int pullRequestNumber, string pullRequestTitle) { return Observable.Defer(() => @@ -629,7 +665,7 @@ public IObservable GetTreeChanges(LocalRepositoryModel repository, { var remote = await gitClient.GetHttpRemote(repo, "origin"); await gitClient.Fetch(repo, remote.Name); - var changes = await gitClient.Compare(repo, pullRequest.BaseRefSha, pullRequest.HeadRefSha, detectRenames: true); + var changes = await gitService.Compare(repo, pullRequest.BaseRefSha, pullRequest.HeadRefSha, detectRenames: true); return Observable.Return(changes); } }); @@ -743,16 +779,22 @@ public async Task ExtractToTempFile( Encoding encoding) { var tempFilePath = CalculateTempFileName(relativePath, commitSha, encoding); + var gitPath = Paths.ToGitPath(relativePath); if (!File.Exists(tempFilePath)) { using (var repo = gitService.GetRepository(repository.LocalPath)) { var remote = await gitClient.GetHttpRemote(repo, "origin"); - await ExtractToTempFile(repo, pullRequest.Number, commitSha, relativePath, encoding, tempFilePath); + await ExtractToTempFile(repo, pullRequest.Number, commitSha, gitPath, encoding, tempFilePath); } } + lock (tempFileMappings) + { + tempFileMappings[CanonicalizeLocalFilePath(tempFilePath)] = (commitSha, gitPath); + } + return tempFilePath; } @@ -825,6 +867,28 @@ public bool ConfirmCancelPendingReview() MessageBoxIcon.Question) == DialogResult.Yes; } + /// + public Task GetObjectishFromLocalPathAsync(string localPath, CancellationToken cancellationToken) + { + lock (this.tempFileMappings) + { + var canonicalizedPath = CanonicalizeLocalFilePath(localPath); + if (this.tempFileMappings.TryGetValue(canonicalizedPath, out (string commitId, string repoPath) result)) + { + return Task.FromResult($"{result.commitId}:{result.repoPath}"); + } + } + + return Task.FromResult(null); + } + + /// + public Task GetLocalPathFromObjectishAsync(string objectish, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + async Task CreateRemote(IRepository repo, UriString cloneUri) { foreach (var remote in repo.Network.Remotes) @@ -1004,6 +1068,12 @@ static string BuildGHfVSConfigKeyValue(string owner, int number) return default; } + static string CanonicalizeLocalFilePath(string localPath) + { + localPath = localPath.Replace("\\\\", "\\"); + return Path.GetFullPath(localPath); + } + class ListItemAdapter : PullRequestListItemModel { public IList Reviews { get; set; } diff --git a/src/GitHub.App/Services/RepositoryCloneService.cs b/src/GitHub.App/Services/RepositoryCloneService.cs index a8bde09b5b..86177cda7d 100644 --- a/src/GitHub.App/Services/RepositoryCloneService.cs +++ b/src/GitHub.App/Services/RepositoryCloneService.cs @@ -4,14 +4,15 @@ using System.IO; using System.Linq; using System.Reactive.Linq; +using System.Threading; using System.Threading.Tasks; using GitHub.Api; using GitHub.Extensions; -using GitHub.Helpers; using GitHub.Logging; using GitHub.Models; using GitHub.Primitives; using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.Threading; using Octokit.GraphQL; using Octokit.GraphQL.Model; using Rothko; @@ -36,7 +37,9 @@ public class RepositoryCloneService : IRepositoryCloneService readonly IVSGitServices vsGitServices; readonly ITeamExplorerServices teamExplorerServices; readonly IGraphQLClientFactory graphqlFactory; + readonly IGitHubContextService gitHubContextService; readonly IUsageTracker usageTracker; + readonly Lazy dte; ICompiledQuery readViewerRepositories; [ImportingConstructor] @@ -45,31 +48,32 @@ public RepositoryCloneService( IVSGitServices vsGitServices, ITeamExplorerServices teamExplorerServices, IGraphQLClientFactory graphqlFactory, - IUsageTracker usageTracker) + IGitHubContextService gitHubContextService, + IUsageTracker usageTracker, + IGitHubServiceProvider sp, + [Import(AllowDefault = true)] JoinableTaskContext joinableTaskContext) { this.operatingSystem = operatingSystem; this.vsGitServices = vsGitServices; this.teamExplorerServices = teamExplorerServices; this.graphqlFactory = graphqlFactory; + this.gitHubContextService = gitHubContextService; this.usageTracker = usageTracker; + dte = new Lazy(() => sp.GetService()); + JoinableTaskContext = joinableTaskContext ?? ThreadHelper.JoinableTaskContext; defaultClonePath = GetLocalClonePathFromGitProvider(operatingSystem.Environment.GetUserRepositoriesPath()); } /// - public async Task ReadViewerRepositories(HostAddress address) + public async Task ReadViewerRepositories(HostAddress address, bool refresh = false) { if (readViewerRepositories == null) { var order = new RepositoryOrder { - Field = RepositoryOrderField.Name, - Direction = OrderDirection.Asc - }; - - var affiliation = new RepositoryAffiliation?[] - { - RepositoryAffiliation.Owner, RepositoryAffiliation.Collaborator + Field = RepositoryOrderField.PushedAt, + Direction = OrderDirection.Desc }; var repositorySelection = new Fragment( @@ -88,28 +92,32 @@ public async Task ReadViewerRepositories(HostAddress ad .Select(viewer => new ViewerRepositoriesModel { Owner = viewer.Login, - Repositories = viewer.Repositories(null, null, null, null, null, order, affiliation, null, null) + Repositories = viewer.Repositories(null, null, null, null, null, null, null, order, null, null) .AllPages() .Select(repositorySelection).ToList(), - OrganizationRepositories = viewer.Organizations(null, null, null, null).AllPages().Select(org => new + ContributedToRepositories = viewer.RepositoriesContributedTo(100, null, null, null, null, null, null, order, null) + .Nodes + .Select(repositorySelection).ToList(), + Organizations = viewer.Organizations(null, null, null, null).AllPages().Select(org => new { org.Login, - Repositories = org.Repositories(null, null, null, null, null, order, null, null, null) - .AllPages() + Repositories = org.Repositories(100, null, null, null, null, null, null, order, null, null) + .Nodes .Select(repositorySelection).ToList() }).ToDictionary(x => x.Login, x => (IReadOnlyList)x.Repositories), }).Compile(); } var graphql = await graphqlFactory.CreateConnection(address).ConfigureAwait(false); - var result = await graphql.Run(readViewerRepositories).ConfigureAwait(false); + var result = await graphql.Run(readViewerRepositories, cacheDuration: TimeSpan.FromHours(1), refresh: refresh).ConfigureAwait(false); return result; } /// public async Task CloneOrOpenRepository( CloneDialogResult cloneDialogResult, - object progress = null) + object progress = null, + CancellationToken? cancellationToken = null) { Guard.ArgumentNotNull(cloneDialogResult, nameof(cloneDialogResult)); @@ -123,9 +131,12 @@ public async Task CloneOrOpenRepository( var repositoryUrl = url.ToRepositoryUrl(); var isDotCom = HostAddress.IsGitHubDotComUri(repositoryUrl); - if (DestinationDirectoryExists(repositoryPath)) + if (DestinationDirectoryExists(repositoryPath) && !DestinationDirectoryEmpty(repositoryPath)) { - teamExplorerServices.OpenRepository(repositoryPath); + if (!IsSolutionInRepository(repositoryPath)) + { + teamExplorerServices.OpenRepository(repositoryPath); + } if (isDotCom) { @@ -139,7 +150,7 @@ public async Task CloneOrOpenRepository( else { var cloneUrl = repositoryUrl.ToString(); - await CloneRepository(cloneUrl, repositoryPath, progress).ConfigureAwait(true); + await CloneRepository(cloneUrl, repositoryPath, progress, cancellationToken).ConfigureAwait(true); if (isDotCom) { @@ -153,26 +164,60 @@ public async Task CloneOrOpenRepository( // Give user a chance to choose a solution teamExplorerServices.ShowHomePage(); + + // Navigate to context for supported URL types (e.g. /blob/ URLs) + var context = gitHubContextService.FindContextFromUrl(url); + if (context != null) + { + gitHubContextService.TryNavigateToContext(repositoryPath, context); + } + } + + bool IsSolutionInRepository(string repositoryPath) + { + var solutionPath = dte.Value.Solution.FileName; + if (string.IsNullOrEmpty(solutionPath)) + { + return false; + } + + var isFolder = operatingSystem.Directory.DirectoryExists(solutionPath); + var solutionDir = isFolder ? solutionPath : Path.GetDirectoryName(solutionPath); + if (string.Equals(repositoryPath, solutionDir, StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + if (solutionDir.StartsWith(repositoryPath + '\\', StringComparison.OrdinalIgnoreCase)) + { + return true; + } + + return false; } /// public async Task CloneRepository( string cloneUrl, string repositoryPath, - object progress = null) + object progress = null, + CancellationToken? cancellationToken = null) { Guard.ArgumentNotEmptyString(cloneUrl, nameof(cloneUrl)); Guard.ArgumentNotEmptyString(repositoryPath, nameof(repositoryPath)); // Switch to a thread pool thread for IO then back to the main thread to call // vsGitServices.Clone() as this must be called on the main thread. - await ThreadingHelper.SwitchToPoolThreadAsync(); - operatingSystem.Directory.CreateDirectory(repositoryPath); - await ThreadingHelper.SwitchToMainThreadAsync(); + if (!DestinationDirectoryExists(repositoryPath)) + { + await TaskScheduler.Default; + operatingSystem.Directory.CreateDirectory(repositoryPath); + await JoinableTaskContext.Factory.SwitchToMainThreadAsync(); + } try { - await vsGitServices.Clone(cloneUrl, repositoryPath, true, progress); + await vsGitServices.Clone(cloneUrl, repositoryPath, true, progress, cancellationToken); await usageTracker.IncrementCounter(x => x.NumberOfClones); if (repositoryPath.StartsWith(DefaultClonePath, StringComparison.OrdinalIgnoreCase)) @@ -184,6 +229,7 @@ public async Task CloneRepository( catch (Exception ex) { log.Error(ex, "Could not clone {CloneUrl} to {Path}", cloneUrl, repositoryPath); + operatingSystem.Directory.DeleteDirectory(repositoryPath); throw; } } @@ -191,6 +237,9 @@ public async Task CloneRepository( /// public bool DestinationDirectoryExists(string path) => operatingSystem.Directory.DirectoryExists(path); + /// + public bool DestinationDirectoryEmpty(string path) => operatingSystem.Directory.GetDirectory(path).IsEmpty; + /// public bool DestinationFileExists(string path) => operatingSystem.File.Exists(path); @@ -204,6 +253,8 @@ string GetLocalClonePathFromGitProvider(string fallbackPath) public string DefaultClonePath { get { return defaultClonePath; } } + JoinableTaskContext JoinableTaskContext { get; } + class OrganizationAdapter { public IReadOnlyList Repositories { get; set; } diff --git a/src/GitHub.App/Services/RepositoryService.cs b/src/GitHub.App/Services/RepositoryService.cs index 36b729fe3e..f713cabbb7 100644 --- a/src/GitHub.App/Services/RepositoryService.cs +++ b/src/GitHub.App/Services/RepositoryService.cs @@ -34,7 +34,7 @@ public RepositoryService(IGraphQLClientFactory graphqlFactory) if (readParentOwnerLogin == null) { readParentOwnerLogin = new Query() - .Repository(Var(nameof(owner)), Var(nameof(name))) + .Repository(owner: Var(nameof(owner)), name: Var(nameof(name))) .Select(r => r.Parent != null ? Tuple.Create(r.Parent.Owner.Login, r.Parent.Name) : null) .Compile(); } diff --git a/src/GitHub.App/Services/TeamExplorerContext.cs b/src/GitHub.App/Services/TeamExplorerContext.cs index 3f3b1caf7e..1a9c1560f3 100644 --- a/src/GitHub.App/Services/TeamExplorerContext.cs +++ b/src/GitHub.App/Services/TeamExplorerContext.cs @@ -49,15 +49,16 @@ public class TeamExplorerContext : ITeamExplorerContext TeamExplorerContext( IVSGitExt gitExt, [Import(typeof(SVsServiceProvider))] IServiceProvider sp, - IPullRequestService pullRequestService) : this( + IPullRequestService pullRequestService, + [Import(AllowDefault = true)] JoinableTaskContext joinableTaskContext) : this( gitExt, new AsyncLazy(async () => { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + await (joinableTaskContext ?? ThreadHelper.JoinableTaskContext).Factory.SwitchToMainThreadAsync(); return (DTE)sp.GetService(typeof(DTE)); }), pullRequestService, - ThreadHelper.JoinableTaskContext) + joinableTaskContext ?? ThreadHelper.JoinableTaskContext) { } diff --git a/src/GitHub.App/ViewModels/CommentThreadViewModel.cs b/src/GitHub.App/ViewModels/CommentThreadViewModel.cs index bd5a9541a8..ce3caf74a2 100644 --- a/src/GitHub.App/ViewModels/CommentThreadViewModel.cs +++ b/src/GitHub.App/ViewModels/CommentThreadViewModel.cs @@ -18,7 +18,6 @@ namespace GitHub.ViewModels /// public abstract class CommentThreadViewModel : ReactiveObject, ICommentThreadViewModel { - readonly ReactiveList comments = new ReactiveList(); readonly Dictionary> draftThrottles = new Dictionary>(); readonly IScheduler timerScheduler; @@ -51,15 +50,9 @@ public CommentThreadViewModel( this.timerScheduler = timerScheduler; } - /// - public IReactiveList Comments => comments; - /// public IActorViewModel CurrentUser { get; private set; } - /// - IReadOnlyReactiveList ICommentThreadViewModel.Comments => comments; - protected IMessageDraftStore DraftStore { get; } /// @@ -72,20 +65,18 @@ public CommentThreadViewModel( public abstract Task DeleteComment(ICommentViewModel comment); /// - /// Adds a placeholder comment that will allow the user to enter a reply, and wires up + /// Initializes a placeholder comment that will allow the user to enter a reply, and wires up /// event listeners for saving drafts. /// /// The placeholder comment view model. /// An object which when disposed will remove the event listeners. - protected IDisposable AddPlaceholder(ICommentViewModel placeholder) + protected IDisposable InitializePlaceholder(ICommentViewModel placeholder) { - Comments.Add(placeholder); - return placeholder.WhenAnyValue( x => x.EditState, x => x.Body, (state, body) => (state, body)) - .Subscribe(x => PlaceholderChanged(placeholder, x.state, x.body)); + .Subscribe(x => PlaceholderChanged(placeholder, x.state)); } /// @@ -120,7 +111,7 @@ protected async Task DeleteDraft(ICommentViewModel comment) protected abstract (string key, string secondaryKey) GetDraftKeys(ICommentViewModel comment); - void PlaceholderChanged(ICommentViewModel placeholder, CommentEditState state, string body) + void PlaceholderChanged(ICommentViewModel placeholder, CommentEditState state) { if (state == CommentEditState.Editing) { diff --git a/src/GitHub.App/ViewModels/CommentViewModel.cs b/src/GitHub.App/ViewModels/CommentViewModel.cs index 259535f0c1..0a750f08a4 100644 --- a/src/GitHub.App/ViewModels/CommentViewModel.cs +++ b/src/GitHub.App/ViewModels/CommentViewModel.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.Composition; using System.Linq; using System.Reactive; using System.Reactive.Linq; @@ -13,13 +14,17 @@ namespace GitHub.ViewModels { /// - /// Base view model for an issue or pull request comment. + /// An issue or pull request comment. /// - public abstract class CommentViewModel : ReactiveObject, ICommentViewModel + [Export(typeof(ICommentViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class CommentViewModel : ViewModelBase, ICommentViewModel { static readonly ILogger log = LogManager.ForContext(); readonly ICommentService commentService; + readonly ObservableAsPropertyHelper canCancel; readonly ObservableAsPropertyHelper canDelete; + ObservableAsPropertyHelper commitCaption; string id; IActorViewModel author; IActorViewModel currentUser; @@ -36,10 +41,14 @@ public abstract class CommentViewModel : ReactiveObject, ICommentViewModel /// Initializes a new instance of the class. /// /// The comment service. - public CommentViewModel(ICommentService commentService) + /// The auto complete advisor. + [ImportingConstructor] + public CommentViewModel(ICommentService commentService, IAutoCompleteAdvisor autoCompleteAdvisor) { Guard.ArgumentNotNull(commentService, nameof(commentService)); + Guard.ArgumentNotNull(autoCompleteAdvisor, nameof(autoCompleteAdvisor)); + AutoCompleteAdvisor = autoCompleteAdvisor; this.commentService = commentService; var canDeleteObservable = this.WhenAnyValue( @@ -70,6 +79,9 @@ public CommentViewModel(ICommentService commentService) (ro, body) => !ro && !string.IsNullOrWhiteSpace(body))); AddErrorHandler(CommitEdit); + canCancel = this.WhenAnyValue(x => x.Id) + .Select(id => id != null) + .ToProperty(this, x => x.CanCancel); CancelEdit = ReactiveCommand.Create(DoCancelEdit, CommitEdit.IsExecuting.Select(x => !x)); AddErrorHandler(CancelEdit); @@ -140,6 +152,9 @@ public bool IsSubmitting protected set => this.RaiseAndSetIfChanged(ref isSubmitting, value); } + /// + public bool CanCancel => canCancel.Value; + /// public bool CanDelete => canDelete.Value; @@ -150,6 +165,9 @@ public DateTimeOffset CreatedAt private set => this.RaiseAndSetIfChanged(ref createdAt, value); } + /// + public string CommitCaption => commitCaption.Value; + /// public ICommentThreadViewModel Thread { @@ -175,14 +193,11 @@ public ICommentThreadViewModel Thread /// public ReactiveCommand Delete { get; } - /// - /// Initializes the view model with data. - /// - /// The thread that the comment is a part of. - /// The current user. - /// The comment model. May be null. - /// The comment edit state. - protected Task InitializeAsync( + /// + public IAutoCompleteAdvisor AutoCompleteAdvisor { get; } + + /// + public Task InitializeAsync( ICommentThreadViewModel thread, ActorModel currentUser, CommentModel comment, @@ -195,13 +210,15 @@ protected Task InitializeAsync( CurrentUser = new ActorViewModel(currentUser); Id = comment?.Id; DatabaseId = comment?.DatabaseId ?? 0; - PullRequestId = comment?.PullRequestId ?? 0; + PullRequestId = (comment as PullRequestReviewCommentModel)?.PullRequestId ?? 0; Body = comment?.Body; EditState = state; Author = comment != null ? new ActorViewModel(comment.Author) : CurrentUser; CreatedAt = comment?.CreatedAt ?? DateTimeOffset.MinValue; WebUrl = comment?.Url != null ? new Uri(comment.Url) : null; + commitCaption = GetCommitCaptionObservable().ToProperty(this, x => x.CommitCaption); + return Task.CompletedTask; } @@ -210,6 +227,12 @@ protected void AddErrorHandler(ReactiveCommand command) command.ThrownExceptions.Subscribe(x => ErrorMessage = x.Message); } + protected virtual IObservable GetCommitCaptionObservable() + { + return this.WhenAnyValue(x => x.Id) + .Select(x => x == null ? Resources.Comment : Resources.UpdateComment); + } + async Task DoDelete() { if (commentService.ConfirmCommentDelete()) diff --git a/src/GitHub.App/ViewModels/CommitActorViewModel.cs b/src/GitHub.App/ViewModels/CommitActorViewModel.cs new file mode 100644 index 0000000000..3f40c05089 --- /dev/null +++ b/src/GitHub.App/ViewModels/CommitActorViewModel.cs @@ -0,0 +1,19 @@ +using GitHub.Models; + +namespace GitHub.ViewModels +{ + public class CommitActorViewModel: ActorViewModel, ICommitActorViewModel + { + public CommitActorViewModel(CommitActorModel model) + :base(model.User) + { + Name = model.Name; + Email = model.Email; + HasLogin = model.User != null; + } + + public string Email { get; } + public string Name { get; } + public bool HasLogin { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/Dialog/Clone/RepositoryCloneViewModel.cs b/src/GitHub.App/ViewModels/Dialog/Clone/RepositoryCloneViewModel.cs index 12c51171e8..1971914a4f 100644 --- a/src/GitHub.App/ViewModels/Dialog/Clone/RepositoryCloneViewModel.cs +++ b/src/GitHub.App/ViewModels/Dialog/Clone/RepositoryCloneViewModel.cs @@ -9,6 +9,7 @@ using GitHub.Extensions; using GitHub.Logging; using GitHub.Models; +using GitHub.Primitives; using GitHub.Services; using ReactiveUI; using Rothko; @@ -20,15 +21,15 @@ namespace GitHub.ViewModels.Dialog.Clone [PartCreationPolicy(CreationPolicy.NonShared)] public class RepositoryCloneViewModel : ViewModelBase, IRepositoryCloneViewModel { - static readonly ILogger log = LogManager.ForContext(); readonly IOperatingSystem os; readonly IConnectionManager connectionManager; readonly IRepositoryCloneService service; readonly IGitService gitService; - readonly IUsageService usageService; readonly IUsageTracker usageTracker; + readonly IDialogService dialogService; readonly IReadOnlyList tabs; string path; + UriString url; RepositoryModel previousRepository; ObservableAsPropertyHelper pathWarning; int selectedTabIndex; @@ -39,23 +40,21 @@ public RepositoryCloneViewModel( IConnectionManager connectionManager, IRepositoryCloneService service, IGitService gitService, - IUsageService usageService, IUsageTracker usageTracker, + IDialogService dialogService, IRepositorySelectViewModel gitHubTab, - IRepositorySelectViewModel enterpriseTab, - IRepositoryUrlViewModel urlTab) + IRepositorySelectViewModel enterpriseTab) { this.os = os; this.connectionManager = connectionManager; this.service = service; this.gitService = gitService; - this.usageService = usageService; this.usageTracker = usageTracker; + this.dialogService = dialogService; GitHubTab = gitHubTab; EnterpriseTab = enterpriseTab; - UrlTab = urlTab; - tabs = new IRepositoryCloneTabViewModel[] { GitHubTab, EnterpriseTab, UrlTab }; + tabs = new IRepositoryCloneTabViewModel[] { GitHubTab, EnterpriseTab }; var repository = this.WhenAnyValue(x => x.SelectedTabIndex) .SelectMany(x => tabs[x].WhenAnyValue(tab => tab.Repository)); @@ -71,11 +70,13 @@ public RepositoryCloneViewModel( var canClone = Observable.CombineLatest( repository, this.WhenAnyValue(x => x.Path), - (repo, path) => repo != null && !service.DestinationFileExists(path) && !service.DestinationDirectoryExists(path)); + (repo, path) => repo != null && !service.DestinationFileExists(path) && + (!service.DestinationDirectoryExists(path) || service.DestinationDirectoryEmpty(path))); var canOpen = Observable.CombineLatest( repository, this.WhenAnyValue(x => x.Path), - (repo, path) => repo != null && !service.DestinationFileExists(path) && service.DestinationDirectoryExists(path)); + (repo, path) => repo != null && !service.DestinationFileExists(path) && service.DestinationDirectoryExists(path) + && !service.DestinationDirectoryEmpty(path)); Browse = ReactiveCommand.Create(() => BrowseForDirectory()); Clone = ReactiveCommand.CreateFromObservable( @@ -84,11 +85,12 @@ public RepositoryCloneViewModel( Open = ReactiveCommand.CreateFromObservable( () => repository.Select(x => new CloneDialogResult(Path, x?.CloneUrl)), canOpen); + + LoginAsDifferentUser = ReactiveCommand.CreateFromTask(LoginAsDifferentUserAsync); } public IRepositorySelectViewModel GitHubTab { get; } public IRepositorySelectViewModel EnterpriseTab { get; } - public IRepositoryUrlViewModel UrlTab { get; } public string Path { @@ -96,6 +98,12 @@ public string Path set => this.RaiseAndSetIfChanged(ref path, value); } + public UriString Url + { + get => url; + set => this.RaiseAndSetIfChanged(ref url, value); + } + public string PathWarning => pathWarning.Value; public int SelectedTabIndex @@ -108,6 +116,8 @@ public int SelectedTabIndex public IObservable Done => Observable.Merge(Clone, Open); + public ReactiveCommand LoginAsDifferentUser { get; } + public ReactiveCommand Browse { get; } public ReactiveCommand Clone { get; } @@ -130,39 +140,52 @@ public async Task InitializeAsync(IConnection connection) EnterpriseTab.Initialize(enterpriseConnection); } - if (connection == enterpriseConnection) + if (connection == gitHubConnection) { - SelectedTabIndex = 1; + SelectedTabIndex = 0; } - - this.WhenAnyValue(x => x.SelectedTabIndex).Subscribe(x => tabs[x].Activate().Forget()); - - // Users in group A will see the URL tab by default - if (await IsGroupA().ConfigureAwait(false)) + else if (connection == enterpriseConnection) { - SelectedTabIndex = 2; + SelectedTabIndex = 1; } - switch (SelectedTabIndex) + if (Url?.Host is string host && HostAddress.Create(host) is HostAddress hostAddress) { - case 0: - usageTracker.IncrementCounter(model => model.NumberOfCloneViewGitHubTab).Forget(); - break; - case 1: - usageTracker.IncrementCounter(model => model.NumberOfCloneViewEnterpriseTab).Forget(); - break; - case 2: - usageTracker.IncrementCounter(model => model.NumberOfCloneViewUrlTab).Forget(); - break; + if (hostAddress == gitHubConnection?.HostAddress) + { + GitHubTab.Filter = Url; + SelectedTabIndex = 0; + } + else if (hostAddress == enterpriseConnection?.HostAddress) + { + EnterpriseTab.Filter = Url; + SelectedTabIndex = 1; + } } + + this.WhenAnyValue(x => x.SelectedTabIndex).Subscribe(x => tabs[x].Activate().Forget()); } - // Put 50% of users in group A - async Task IsGroupA() + async Task LoginAsDifferentUserAsync() { - var userGuid = await usageService.GetUserGuid().ConfigureAwait(false); - var lastByte = userGuid.ToByteArray().Last(); - return lastByte % 2 == 0; + if (await dialogService.ShowLoginDialog() is IConnection connection) + { + var connections = await connectionManager.GetLoadedConnections(); + var gitHubConnection = connections.FirstOrDefault(x => x.HostAddress.IsGitHubDotCom()); + + if (connection == gitHubConnection) + { + SelectedTabIndex = 0; + GitHubTab.Initialize(connection); + GitHubTab.Activate().Forget(); + } + else + { + SelectedTabIndex = 1; + EnterpriseTab.Initialize(connection); + EnterpriseTab.Activate().Forget(); + } + } } void BrowseForDirectory() @@ -248,13 +271,13 @@ string ValidatePathWarning(RepositoryModel repositoryModel, string path) return Resources.DestinationAlreadyExists; } - if (service.DestinationDirectoryExists(path)) + if (service.DestinationDirectoryExists(path) && !service.DestinationDirectoryEmpty(path)) { using (var repository = gitService.GetRepository(path)) { if (repository == null) { - return Resources.CantFindARepositoryAtLocalPath; + return Resources.DirectoryAtDestinationNotEmpty; } var localUrl = gitService.GetRemoteUri(repository)?.ToRepositoryUrl(); @@ -266,7 +289,8 @@ string ValidatePathWarning(RepositoryModel repositoryModel, string path) var targetUrl = repositoryModel.CloneUrl?.ToRepositoryUrl(); if (localUrl != targetUrl) { - return string.Format(CultureInfo.CurrentCulture, Resources.LocalRepositoryHasARemoteOf, localUrl); + return string.Format(CultureInfo.CurrentCulture, Resources.LocalRepositoryHasARemoteOf, + localUrl); } return Resources.YouHaveAlreadyClonedToThisLocation; diff --git a/src/GitHub.App/ViewModels/Dialog/Clone/RepositorySelectViewModel.cs b/src/GitHub.App/ViewModels/Dialog/Clone/RepositorySelectViewModel.cs index 539b5d7f0a..e3f391f412 100644 --- a/src/GitHub.App/ViewModels/Dialog/Clone/RepositorySelectViewModel.cs +++ b/src/GitHub.App/ViewModels/Dialog/Clone/RepositorySelectViewModel.cs @@ -2,10 +2,13 @@ using System.Collections.Generic; using System.ComponentModel; using System.ComponentModel.Composition; +using System.Globalization; using System.Linq; +using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; using System.Windows.Data; +using GitHub.Exports; using GitHub.Extensions; using GitHub.Logging; using GitHub.Models; @@ -22,27 +25,37 @@ public class RepositorySelectViewModel : ViewModelBase, IRepositorySelectViewMod { static readonly ILogger log = LogManager.ForContext(); readonly IRepositoryCloneService service; + readonly IGitHubContextService gitHubContextService; IConnection connection; Exception error; string filter; bool isEnabled; bool isLoading; - bool loadingStarted; IReadOnlyList items; ICollectionView itemsView; ObservableAsPropertyHelper repository; IRepositoryItemViewModel selectedItem; [ImportingConstructor] - public RepositorySelectViewModel(IRepositoryCloneService service) + public RepositorySelectViewModel(IRepositoryCloneService service, IGitHubContextService gitHubContextService) { Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(service, nameof(gitHubContextService)); this.service = service; + this.gitHubContextService = gitHubContextService; - repository = this.WhenAnyValue(x => x.SelectedItem) - .Select(CreateRepository) + var selectedRepository = this.WhenAnyValue(x => x.SelectedItem) + .Select(CreateRepository); + + var filterRepository = this.WhenAnyValue(x => x.Filter) + .Select(f => gitHubContextService.FindContextFromUrl(f)) + .Select(CreateRepository); + + repository = selectedRepository + .Merge(filterRepository) .ToProperty(this, x => x.Repository); + this.WhenAnyValue(x => x.Filter).Subscribe(_ => ItemsView?.Refresh()); } @@ -100,27 +113,56 @@ public void Initialize(IConnection connection) public async Task Activate() { - if (connection == null || loadingStarted) return; + await this.LoadItems(true); + } + + static string GroupName(KeyValuePair> group, int max) + { + var name = group.Key; + if (group.Value.Count == max) + { + name += $" ({string.Format(CultureInfo.InvariantCulture, Resources.MostRecentlyPushed, max)})"; + } + + return name; + } + + async Task LoadItems(bool refresh) + { + if (connection == null || IsLoading) return; Error = null; IsLoading = true; - loadingStarted = true; try { - var results = await service.ReadViewerRepositories(connection.HostAddress).ConfigureAwait(true); + if (refresh) + { + Items = new List(); + ItemsView = CollectionViewSource.GetDefaultView(Items); + } + + var results = await log.TimeAsync(nameof(service.ReadViewerRepositories), + () => service.ReadViewerRepositories(connection.HostAddress, refresh)); var yourRepositories = results.Repositories .Where(r => r.Owner == results.Owner) - .Select(x => new RepositoryItemViewModel(x, "Your repositories")); + .Select(x => new RepositoryItemViewModel(x, Resources.RepositorySelectYourRepositories)); var collaboratorRepositories = results.Repositories .Where(r => r.Owner != results.Owner) .OrderBy(r => r.Owner) - .Select(x => new RepositoryItemViewModel(x, "Collaborator repositories")); - var orgRepositories = results.OrganizationRepositories + .Select(x => new RepositoryItemViewModel(x, Resources.RepositorySelectCollaboratorRepositories)); + var repositoriesContributedTo = results.ContributedToRepositories + .Select(x => new RepositoryItemViewModel(x, Resources.RepositorySelectContributedRepositories)); + var orgRepositories = results.Organizations .OrderBy(x => x.Key) - .SelectMany(x => x.Value.Select(y => new RepositoryItemViewModel(y, x.Key))); - Items = yourRepositories.Concat(collaboratorRepositories).Concat(orgRepositories).ToList(); + .SelectMany(x => x.Value.Select(y => new RepositoryItemViewModel(y, GroupName(x, 100)))); + Items = yourRepositories + .Concat(collaboratorRepositories) + .Concat(repositoriesContributedTo) + .Concat(orgRepositories) + .ToList(); + log.Information("Read {Total} viewer repositories", Items.Count); ItemsView = CollectionViewSource.GetDefaultView(Items); ItemsView.GroupDescriptions.Add(new PropertyGroupDescription(nameof(RepositoryItemViewModel.Group))); ItemsView.Filter = FilterItem; @@ -144,9 +186,24 @@ public async Task Activate() bool FilterItem(object obj) { - if (obj is IRepositoryItemViewModel item && !string.IsNullOrWhiteSpace(Filter)) + var trimedFilter = Filter?.Trim(); + if (obj is IRepositoryItemViewModel item && !string.IsNullOrEmpty(trimedFilter)) { - return item.Caption.Contains(Filter, StringComparison.CurrentCultureIgnoreCase); + if (new UriString(trimedFilter).IsHypertextTransferProtocol) + { + var urlString = item.Url.ToString(); + var urlStringWithGit = urlString + ".git"; + var urlStringWithSlash = urlString + "/"; + return + urlString.Contains(trimedFilter, StringComparison.OrdinalIgnoreCase) || + urlStringWithGit.Contains(trimedFilter, StringComparison.OrdinalIgnoreCase) || + urlStringWithSlash.Contains(trimedFilter, StringComparison.OrdinalIgnoreCase); + } + else + { + return + item.Caption.Contains(trimedFilter, StringComparison.CurrentCultureIgnoreCase); + } } return true; @@ -158,5 +215,17 @@ RepositoryModel CreateRepository(IRepositoryItemViewModel item) new RepositoryModel(item.Name, UriString.ToUriString(item.Url)) : null; } + + RepositoryModel CreateRepository(GitHubContext context) + { + switch (context?.LinkType) + { + case LinkType.Repository: + case LinkType.Blob: + return new RepositoryModel(context.RepositoryName, context.Url); + } + + return null; + } } } diff --git a/src/GitHub.App/ViewModels/Dialog/Clone/RepositoryUrlViewModel.cs b/src/GitHub.App/ViewModels/Dialog/Clone/RepositoryUrlViewModel.cs deleted file mode 100644 index 0c2d69caee..0000000000 --- a/src/GitHub.App/ViewModels/Dialog/Clone/RepositoryUrlViewModel.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Threading.Tasks; -using GitHub.Extensions; -using GitHub.Models; -using GitHub.Primitives; -using GitHub.ViewModels; -using GitHub.ViewModels.Dialog.Clone; -using ReactiveUI; - -namespace GitHub.App.ViewModels.Dialog.Clone -{ - [Export(typeof(IRepositoryUrlViewModel))] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class RepositoryUrlViewModel : ViewModelBase, IRepositoryUrlViewModel - { - ObservableAsPropertyHelper repository; - string url; - - public RepositoryUrlViewModel() - { - repository = this.WhenAnyValue(x => x.Url, ParseUrl).ToProperty(this, x => x.Repository); - } - - public string Url - { - get => url; - set => this.RaiseAndSetIfChanged(ref url, value); - } - - public bool IsEnabled => true; - - public RepositoryModel Repository => repository.Value; - - public Task Activate() => Task.CompletedTask; - - RepositoryModel ParseUrl(string s) - { - if (s != null) - { - var uri = new UriString(s); - - if (string.IsNullOrWhiteSpace(uri.Owner) || !string.IsNullOrWhiteSpace(uri.RepositoryName)) - { - var parts = s.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); - - if (parts.Length == 2) - { - uri = UriString.ToUriString( - HostAddress.GitHubDotComHostAddress.WebUri - .Append(parts[0]) - .Append(parts[1])); - } - } - - if (!string.IsNullOrWhiteSpace(uri.Owner) && !string.IsNullOrWhiteSpace(uri.RepositoryName)) - { - return new RepositoryModel(uri.RepositoryName, uri); - } - } - - return null; - } - } -} diff --git a/src/GitHub.App/ViewModels/Dialog/GitHubDialogWindowViewModel.cs b/src/GitHub.App/ViewModels/Dialog/GitHubDialogWindowViewModel.cs index 7423c62cb3..479e28b38a 100644 --- a/src/GitHub.App/ViewModels/Dialog/GitHubDialogWindowViewModel.cs +++ b/src/GitHub.App/ViewModels/Dialog/GitHubDialogWindowViewModel.cs @@ -81,7 +81,7 @@ public async Task StartWithConnection(T viewModel) } } - public async Task StartWithLogout(T viewModel, IConnection connection) + public Task StartWithLogout(T viewModel, IConnection connection) where T : IDialogContentViewModel, IConnectionInitializedViewModel { var logout = factory.CreateViewModel(); @@ -101,6 +101,7 @@ public async Task StartWithLogout(T viewModel, IConnection connection) }); Content = logout; + return Task.CompletedTask; } async Task ShowLogin() diff --git a/src/GitHub.App/ViewModels/Dialog/LoginCredentialsViewModel.cs b/src/GitHub.App/ViewModels/Dialog/LoginCredentialsViewModel.cs index 2b071a9f32..fce1a65368 100644 --- a/src/GitHub.App/ViewModels/Dialog/LoginCredentialsViewModel.cs +++ b/src/GitHub.App/ViewModels/Dialog/LoginCredentialsViewModel.cs @@ -1,8 +1,6 @@ using System; using System.ComponentModel.Composition; using System.Reactive.Linq; -using GitHub.App; -using GitHub.Primitives; using GitHub.Services; using ReactiveUI; @@ -28,8 +26,7 @@ public LoginCredentialsViewModel( (x, y) => x.Value || y.Value ).ToProperty(this, vm => vm.IsLoginInProgress); - UpdateLoginMode(); - connectionManager.Connections.CollectionChanged += (_, __) => UpdateLoginMode(); + LoginMode = LoginMode.DotComOrEnterprise; Done = Observable.Merge( loginToGitHubViewModel.Login, @@ -55,21 +52,5 @@ public LoginMode LoginMode public bool IsLoginInProgress { get { return isLoginInProgress.Value; } } public IObservable Done { get; } - - void UpdateLoginMode() - { - var result = LoginMode.DotComOrEnterprise; - - foreach (var connection in ConnectionManager.Connections) - { - if (connection.IsLoggedIn) - { - result &= ~((connection.HostAddress == HostAddress.GitHubDotComHostAddress) ? - LoginMode.DotComOnly : LoginMode.EnterpriseOnly); - } - } - - LoginMode = result; - } } } diff --git a/src/GitHub.App/ViewModels/Dialog/RepositoryCreationViewModel.cs b/src/GitHub.App/ViewModels/Dialog/RepositoryCreationViewModel.cs index d2dca35e3c..e31758de75 100644 --- a/src/GitHub.App/ViewModels/Dialog/RepositoryCreationViewModel.cs +++ b/src/GitHub.App/ViewModels/Dialog/RepositoryCreationViewModel.cs @@ -39,7 +39,6 @@ public class RepositoryCreationViewModel : RepositoryFormViewModel, IRepositoryC readonly IModelServiceFactory modelServiceFactory; readonly IRepositoryCreationService repositoryCreationService; readonly ObservableAsPropertyHelper isCreating; - readonly ObservableAsPropertyHelper canKeepPrivate; readonly IOperatingSystem operatingSystem; readonly IUsageTracker usageTracker; ObservableAsPropertyHelper> accounts; @@ -91,15 +90,8 @@ public RepositoryCreationViewModel( return parsedReference != repoName ? String.Format(CultureInfo.CurrentCulture, Resources.SafeRepositoryNameWarning, parsedReference) : null; }); - this.WhenAny(x => x.BaseRepositoryPathValidator.ValidationResult, x => x.Value) - .Subscribe(); - CreateRepository = InitializeCreateRepositoryCommand(); - canKeepPrivate = CanKeepPrivateObservable.CombineLatest(CreateRepository.IsExecuting, - (canKeep, publishing) => canKeep && !publishing) - .ToProperty(this, x => x.CanKeepPrivate); - isCreating = CreateRepository.IsExecuting .ToProperty(this, x => x.IsCreating); @@ -128,11 +120,6 @@ public string BaseRepositoryPath /// public bool IsCreating { get { return isCreating.Value; } } - /// - /// If the repo can be made private (depends on the user plan) - /// - public bool CanKeepPrivate { get { return canKeepPrivate.Value; } } - IReadOnlyList gitIgnoreTemplates; public IReadOnlyList GitIgnoreTemplates { diff --git a/src/GitHub.App/ViewModels/Dialog/RepositoryRecloneViewModel.cs b/src/GitHub.App/ViewModels/Dialog/RepositoryRecloneViewModel.cs deleted file mode 100644 index f996cd5b70..0000000000 --- a/src/GitHub.App/ViewModels/Dialog/RepositoryRecloneViewModel.cs +++ /dev/null @@ -1,162 +0,0 @@ -using System; -using System.ComponentModel.Composition; -using System.Globalization; -using System.IO; -using System.Reactive; -using System.Reactive.Linq; -using System.Threading.Tasks; -using System.Windows.Input; -using GitHub.App; -using GitHub.Extensions; -using GitHub.Logging; -using GitHub.Models; -using GitHub.Services; -using GitHub.Validation; -using ReactiveUI; -using Rothko; -using Serilog; - -namespace GitHub.ViewModels.Dialog -{ - [Export(typeof(IRepositoryRecloneViewModel))] - [PartCreationPolicy(CreationPolicy.NonShared)] - public class RepositoryRecloneViewModel : ViewModelBase, IRepositoryRecloneViewModel - { - static readonly ILogger log = LogManager.ForContext(); - - readonly IOperatingSystem operatingSystem; - readonly ReactiveCommand browseForDirectoryCommand = ReactiveCommand.Create(() => { }); - readonly ObservableAsPropertyHelper canClone; - string baseRepositoryPath; - - [ImportingConstructor] - public RepositoryRecloneViewModel( - IRepositoryCloneService cloneService, - IOperatingSystem operatingSystem) - { - Guard.ArgumentNotNull(cloneService, nameof(cloneService)); - Guard.ArgumentNotNull(operatingSystem, nameof(operatingSystem)); - - this.operatingSystem = operatingSystem; - - var baseRepositoryPath = this.WhenAny( - x => x.BaseRepositoryPath, - x => x.SelectedRepository, - (x, y) => x.Value); - - BaseRepositoryPathValidator = ReactivePropertyValidator.ForObservable(baseRepositoryPath) - .IfNullOrEmpty(Resources.RepositoryCreationClonePathEmpty) - .IfTrue(x => x.Length > 200, Resources.RepositoryCreationClonePathTooLong) - .IfContainsInvalidPathChars(Resources.RepositoryCreationClonePathInvalidCharacters) - .IfPathNotRooted(Resources.RepositoryCreationClonePathInvalid) - .IfTrue(IsAlreadyRepoAtPath, Resources.RepositoryNameValidatorAlreadyExists); - - var canCloneObservable = this.WhenAny( - x => x.SelectedRepository, - x => x.BaseRepositoryPathValidator.ValidationResult.IsValid, - (x, y) => x.Value != null && y.Value); - canClone = canCloneObservable.ToProperty(this, x => x.CanClone); - CloneCommand = ReactiveCommand.Create(() => { }, canCloneObservable); - - browseForDirectoryCommand.Subscribe(_ => ShowBrowseForDirectoryDialog()); - this.WhenAny(x => x.BaseRepositoryPathValidator.ValidationResult, x => x.Value) - .Subscribe(); - BaseRepositoryPath = cloneService.DefaultClonePath; - } - - public Task InitializeAsync(IConnection connection) - { - Title = string.Format(CultureInfo.CurrentCulture, Resources.CloneTitle, connection.HostAddress.Title); - return Task.CompletedTask; - } - - bool IsAlreadyRepoAtPath(string path) - { - Guard.ArgumentNotNull(path, nameof(path)); - - bool isAlreadyRepoAtPath = false; - - if (SelectedRepository != null) - { - string potentialPath = Path.Combine(path, SelectedRepository.Name); - isAlreadyRepoAtPath = operatingSystem.Directory.Exists(potentialPath); - } - - return isAlreadyRepoAtPath; - } - - IObservable ShowBrowseForDirectoryDialog() - { - return Observable.Start(() => - { - // We store this in a local variable to prevent it changing underneath us while the - // folder dialog is open. - var localBaseRepositoryPath = BaseRepositoryPath; - var browseResult = operatingSystem.Dialog.BrowseForDirectory(localBaseRepositoryPath, Resources.BrowseForDirectory); - - if (!browseResult.Success) - return; - - var directory = browseResult.DirectoryPath ?? localBaseRepositoryPath; - - try - { - BaseRepositoryPath = directory; - } - catch (Exception e) - { - // TODO: We really should limit this to exceptions we know how to handle. - log.Error(e, "Failed to set base repository path. localBaseRepositoryPath = {0} BaseRepositoryPath = {1} Chosen directory = {2}", - localBaseRepositoryPath ?? "(null)", BaseRepositoryPath ?? "(null)", directory ?? "(null)"); - } - }, RxApp.MainThreadScheduler); - } - - /// - /// Gets the dialog title. - /// - public string Title { get; private set; } - - /// - /// Path to clone repositories into - /// - public string BaseRepositoryPath - { - get { return baseRepositoryPath; } - set { this.RaiseAndSetIfChanged(ref baseRepositoryPath, value); } - } - - /// - /// Signals that the user clicked the clone button. - /// - public ReactiveCommand CloneCommand { get; private set; } - - RepositoryModel selectedRepository; - /// - /// Selected repository to clone - /// - public RepositoryModel SelectedRepository - { - get { return selectedRepository; } - set { this.RaiseAndSetIfChanged(ref selectedRepository, value); } - } - - public ICommand BrowseForDirectory - { - get { return browseForDirectoryCommand; } - } - - public bool CanClone - { - get { return canClone.Value; } - } - - public ReactivePropertyValidator BaseRepositoryPathValidator - { - get; - private set; - } - - public IObservable Done => CloneCommand.Select(_ => BaseRepositoryPath); - } -} diff --git a/src/GitHub.App/ViewModels/Documents/CommitListViewModel.cs b/src/GitHub.App/ViewModels/Documents/CommitListViewModel.cs new file mode 100644 index 0000000000..982661a556 --- /dev/null +++ b/src/GitHub.App/ViewModels/Documents/CommitListViewModel.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Text; + +namespace GitHub.ViewModels.Documents +{ + /// + /// Displays a list of commit summaries in a pull request timeline. + /// + [Export(typeof(ICommitListViewModel))] + public class CommitListViewModel : ViewModelBase, ICommitListViewModel + { + /// + /// Initializes a new instance of the class. + /// + /// The commits to display. + public CommitListViewModel(params ICommitSummaryViewModel[] commits) + { + if (commits.Length == 0) + { + throw new NotSupportedException("Cannot create a CommitListViewModel with 0 commits."); + } + + Commits = commits; + Author = Commits[0].Author; + AuthorName = GetAuthorDisplayName(Commits[0].Author); + AuthorCaption = BuildAuthorCaption(); + } + + /// + /// Initializes a new instance of the class. + /// + /// The commits to display. + public CommitListViewModel(IEnumerable commits) + { + Commits = commits.ToList(); + + if (Commits.Count == 0) + { + throw new NotSupportedException("Cannot create a CommitListViewModel with 0 commits."); + } + + Author = Commits[0].Author; + AuthorName = GetAuthorDisplayName(Commits[0].Author); + AuthorCaption = BuildAuthorCaption(); + } + + /// + public ICommitActorViewModel Author { get; } + + /// + public string AuthorName { get; } + + /// + public string AuthorCaption { get; } + + /// + public IReadOnlyList Commits { get; } + + string BuildAuthorCaption() + { + var result = new StringBuilder(); + + if (Commits.Any(x => GetAuthorDisplayName(x.Author) != AuthorName)) + { + result.Append(Resources.AndOthers); + result.Append(' '); + } + + result.Append(Resources.AddedSomeCommits); + return result.ToString(); + } + + string GetAuthorDisplayName(ICommitActorViewModel commitActorViewModel) + { + return commitActorViewModel.HasLogin ? commitActorViewModel.Login : commitActorViewModel.Name; + } + } +} diff --git a/src/GitHub.App/ViewModels/Documents/CommitSummaryViewModel.cs b/src/GitHub.App/ViewModels/Documents/CommitSummaryViewModel.cs new file mode 100644 index 0000000000..7cb9b10e0b --- /dev/null +++ b/src/GitHub.App/ViewModels/Documents/CommitSummaryViewModel.cs @@ -0,0 +1,34 @@ +using GitHub.Models; + +namespace GitHub.ViewModels.Documents +{ + /// + /// Displays a one-line summary of a commit in a pull request timeline. + /// + public class CommitSummaryViewModel : ViewModelBase, ICommitSummaryViewModel + { + /// + /// Initializes a new instance of the class. + /// + /// The commit model. + public CommitSummaryViewModel(CommitModel commit) + { + AbbreviatedOid = commit.AbbreviatedOid; + Author = new CommitActorViewModel(commit.Author); + Header = commit.MessageHeadline; + Oid = commit.Oid; + } + + /// + public string AbbreviatedOid { get; private set; } + + /// + public ICommitActorViewModel Author { get; private set; } + + /// + public string Header { get; private set; } + + /// + public string Oid { get; private set; } + } +} diff --git a/src/GitHub.App/ViewModels/Documents/IIssueishCommentViewModel.cs b/src/GitHub.App/ViewModels/Documents/IIssueishCommentViewModel.cs new file mode 100644 index 0000000000..08bf9e1f1c --- /dev/null +++ b/src/GitHub.App/ViewModels/Documents/IIssueishCommentViewModel.cs @@ -0,0 +1,55 @@ +using System; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.Documents +{ + /// + /// View model for comments on an issue or pull request. + /// + public interface IIssueishCommentViewModel : ICommentViewModel, IDisposable + { + /// + /// Gets a value indicating whether the comment will show a button for + /// . + /// + bool CanCloseOrReopen { get; } + + /// + /// Gets a a caption for the command. + /// + string CloseOrReopenCaption { get; } + + /// + /// Gets a command which when executed will close the issue or pull request if it is open, + /// or reopen it if it is closed. + /// + ReactiveCommand CloseOrReopen { get; } + + /// + /// Initializes the view model with data. + /// + /// The thread that the comment is a part of. + /// The current user. + /// The comment model. May be null. + /// + /// true if the comment is on a pull request, false if the comment is on an issue. + /// + /// + /// Whether the user can close or reopen the pull request from this comment. + /// + /// + /// An observable tracking whether the issue or pull request is open. Can be null if + /// is false. + /// + Task InitializeAsync( + IIssueishCommentThreadViewModel thread, + ActorModel currentUser, + CommentModel comment, + bool isPullRequest, + bool canCloseOrReopen, + IObservable isOpen = null); + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/Documents/IssueishCommentViewModel.cs b/src/GitHub.App/ViewModels/Documents/IssueishCommentViewModel.cs new file mode 100644 index 0000000000..44db887114 --- /dev/null +++ b/src/GitHub.App/ViewModels/Documents/IssueishCommentViewModel.cs @@ -0,0 +1,104 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive; +using System.Reactive.Linq; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.Documents +{ + /// + /// View model for comments on an issue or pull request. + /// + [Export(typeof(IIssueishCommentViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public sealed class IssueishCommentViewModel : CommentViewModel, IIssueishCommentViewModel + { + bool canCloseOrReopen; + ObservableAsPropertyHelper closeOrReopenCaption; + + /// + /// Initializes a new instance of the class. + /// + /// The comment service. + /// + [ImportingConstructor] + public IssueishCommentViewModel(ICommentService commentService, IAutoCompleteAdvisor autoCompleteAdvisor) + : base(commentService, autoCompleteAdvisor) + { + CloseOrReopen = ReactiveCommand.CreateFromTask( + DoCloseOrReopen, + this.WhenAnyValue(x => x.CanCloseOrReopen)); + AddErrorHandler(CloseOrReopen); + } + + /// + public bool CanCloseOrReopen + { + get => canCloseOrReopen; + private set => this.RaiseAndSetIfChanged(ref canCloseOrReopen, value); + } + + /// + public string CloseOrReopenCaption => closeOrReopenCaption?.Value; + + /// + public ReactiveCommand CloseOrReopen { get; } + + /// + public async Task InitializeAsync( + IIssueishCommentThreadViewModel thread, + ActorModel currentUser, + CommentModel comment, + bool isPullRequest, + bool canCloseOrReopen, + IObservable isOpen = null) + { + await base.InitializeAsync( + thread, + currentUser, + comment, + comment == null ? CommentEditState.Editing : CommentEditState.None) + .ConfigureAwait(true); + + CanCloseOrReopen = canCloseOrReopen; + closeOrReopenCaption?.Dispose(); + + if (canCloseOrReopen && isOpen != null) + { + closeOrReopenCaption = + this.WhenAnyValue(x => x.Body) + .CombineLatest(isOpen, (body, open) => GetCloseOrReopenCaption(isPullRequest, open, body)) + .ToProperty(this, x => x.CloseOrReopenCaption); + } + } + + public void Dispose() => closeOrReopenCaption?.Dispose(); + + async Task DoCloseOrReopen() + { + await ((IIssueishCommentThreadViewModel)Thread).CloseOrReopen(this).ConfigureAwait(true); + } + + static string GetCloseOrReopenCaption(bool isPullRequest, bool isOpen, string body) + { + if (string.IsNullOrEmpty(body)) + { + if (isPullRequest) + { + return isOpen ? Resources.ClosePullRequest : Resources.ReopenPullRequest; + } + else + { + return isOpen ? Resources.CloseIssue: Resources.ReopenIssue; + } + } + else + { + return isOpen ? Resources.CloseAndComment : Resources.ReopenAndComment; + } + } + } +} diff --git a/src/GitHub.App/ViewModels/Documents/IssueishPaneViewModel.cs b/src/GitHub.App/ViewModels/Documents/IssueishPaneViewModel.cs new file mode 100644 index 0000000000..9e7650c19d --- /dev/null +++ b/src/GitHub.App/ViewModels/Documents/IssueishPaneViewModel.cs @@ -0,0 +1,86 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.Documents +{ + [Export(typeof(IIssueishPaneViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class IssueishPaneViewModel : ViewModelBase, IIssueishPaneViewModel + { + readonly IViewViewModelFactory factory; + readonly IPullRequestSessionManager sessionManager; + IViewModel content; + string paneCaption; + + [ImportingConstructor] + public IssueishPaneViewModel( + IViewViewModelFactory factory, + IPullRequestSessionManager sessionManager) + { + Guard.ArgumentNotNull(factory, nameof(factory)); + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + + this.factory = factory; + this.sessionManager = sessionManager; + } + + public IViewModel Content + { + get => content; + private set => this.RaiseAndSetIfChanged(ref content, value); + } + + public bool IsInitialized => content != null; + + public string PaneCaption + { + get => paneCaption; + private set => this.RaiseAndSetIfChanged(ref paneCaption, value); + } + + public Task InitializeAsync(IServiceProvider paneServiceProvider) + { + return Task.CompletedTask; + } + + public async Task Load(IConnection connection, string owner, string name, int number) + { + Content = new SpinnerViewModel(); + PaneCaption = "#" + number; + + // TODO: We will eventually support loading issues here as well. + try + { + var session = await sessionManager.GetSession(owner, name, number).ConfigureAwait(true); + var vm = factory.CreateViewModel(); + + var repository = new RemoteRepositoryModel( + 0, + name, + session.LocalRepository.CloneUrl.WithOwner(session.PullRequest.HeadRepositoryOwner), + false, + false, + null, + null); + + await vm.InitializeAsync( + repository, + session.LocalRepository, + session.User, + session.PullRequest).ConfigureAwait(true); + Content = vm; + PaneCaption += " " + vm.Title; + } + catch (Exception ex) + { + // TODO: Show exception. + } + } + } +} diff --git a/src/GitHub.App/ViewModels/Documents/PullRequestPageViewModel.cs b/src/GitHub.App/ViewModels/Documents/PullRequestPageViewModel.cs new file mode 100644 index 0000000000..879779792d --- /dev/null +++ b/src/GitHub.App/ViewModels/Documents/PullRequestPageViewModel.cs @@ -0,0 +1,216 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Factories; +using GitHub.Models; +using GitHub.Primitives; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.Documents +{ + /// + /// View model for displaying a pull request in a document window. + /// + [Export(typeof(IPullRequestPageViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class PullRequestPageViewModel : PullRequestViewModelBase, IPullRequestPageViewModel, IIssueishCommentThreadViewModel + { + readonly IViewViewModelFactory factory; + readonly IPullRequestService service; + readonly IPullRequestSessionManager sessionManager; + readonly ITeamExplorerServices teServices; + readonly IVisualStudioBrowser visualStudioBrowser; + readonly IUsageTracker usageTracker; + ActorModel currentUserModel; + ReactiveList timeline = new ReactiveList(); + + /// + /// Initializes a new instance of the class. + /// + /// The view model factory. + [ImportingConstructor] + public PullRequestPageViewModel( + IViewViewModelFactory factory, + IPullRequestService service, + IPullRequestSessionManager sessionManager, + ITeamExplorerServices teServices, + IVisualStudioBrowser visualStudioBrowser, + IUsageTracker usageTracker) + { + Guard.ArgumentNotNull(factory, nameof(factory)); + Guard.ArgumentNotNull(service, nameof(service)); + Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); + Guard.ArgumentNotNull(visualStudioBrowser, nameof(visualStudioBrowser)); + Guard.ArgumentNotNull(teServices, nameof(teServices)); + + this.factory = factory; + this.service = service; + this.sessionManager = sessionManager; + this.teServices = teServices; + this.visualStudioBrowser = visualStudioBrowser; + this.usageTracker = usageTracker; + + timeline.ItemsRemoved.Subscribe(TimelineItemRemoved); + + ShowCommit = ReactiveCommand.CreateFromTask(DoShowCommit); + OpenOnGitHub = ReactiveCommand.Create(DoOpenOnGitHub); + } + + /// + public IActorViewModel CurrentUser { get; private set; } + + /// + public int CommitCount { get; private set; } + + /// + public IReadOnlyList Timeline => timeline; + + /// + public ReactiveCommand ShowCommit { get; } + + /// + public async Task InitializeAsync( + RemoteRepositoryModel repository, + LocalRepositoryModel localRepository, + ActorModel currentUser, + PullRequestDetailModel model) + { + await base.InitializeAsync(repository, localRepository, model).ConfigureAwait(true); + + timeline.Clear(); + CommitCount = 0; + currentUserModel = currentUser; + CurrentUser = new ActorViewModel(currentUser); + + var commits = new List(); + + foreach (var i in model.Timeline) + { + if (!(i is CommitModel) && commits.Count > 0) + { + timeline.Add(new CommitListViewModel(commits)); + commits.Clear(); + } + + switch (i) + { + case CommitModel commit: + commits.Add(new CommitSummaryViewModel(commit)); + ++CommitCount; + break; + case CommentModel comment: + await AddComment(comment).ConfigureAwait(true); + break; + } + } + + if (commits.Count > 0) + { + timeline.Add(new CommitListViewModel(commits)); + } + + await AddPlaceholder().ConfigureAwait(true); + await usageTracker.IncrementCounter(x => x.NumberOfPRConversationsOpened); + } + + /// + public async Task CloseOrReopen(ICommentViewModel comment) + { + var address = HostAddress.Create(Repository.CloneUrl); + + if (State == PullRequestState.Open) + { + await service.CloseIssueish( + address, + Repository.Owner, + Repository.Name, + Number).ConfigureAwait(true); + State = PullRequestState.Closed; + } + else + { + await service.ReopenIssueish( + address, + Repository.Owner, + Repository.Name, + Number).ConfigureAwait(true); + State = PullRequestState.Open; + } + } + + /// + public async Task PostComment(ICommentViewModel comment) + { + var address = HostAddress.Create(Repository.CloneUrl); + var result = await service.PostComment(address, Id, comment.Body).ConfigureAwait(true); + timeline.Remove(comment); + await AddComment(result).ConfigureAwait(true); + await AddPlaceholder().ConfigureAwait(true); + } + + public async Task DeleteComment(ICommentViewModel comment) + { + await service.DeleteComment( + HostAddress.Create(Repository.CloneUrl), + Repository.Owner, + Repository.Name, + comment.DatabaseId).ConfigureAwait(true); + timeline.Remove(comment); + } + + public async Task EditComment(ICommentViewModel comment) + { + await service.EditComment( + HostAddress.Create(Repository.CloneUrl), + Repository.Owner, + Repository.Name, + comment.DatabaseId, + comment.Body).ConfigureAwait(false); + } + + async Task AddComment(CommentModel comment) + { + var vm = factory.CreateViewModel(); + await vm.InitializeAsync( + this, + currentUserModel, + comment, + true, + false).ConfigureAwait(true); + timeline.Add(vm); + } + + async Task AddPlaceholder() + { + var placeholder = factory.CreateViewModel(); + await placeholder.InitializeAsync( + this, + currentUserModel, + null, + true, + true, + this.WhenAnyValue(x => x.State, x => x == PullRequestState.Open)).ConfigureAwait(true); + timeline.Add(placeholder); + } + + async Task DoShowCommit(string oid) + { + await service.FetchCommit(LocalRepository, Repository, oid).ConfigureAwait(true); + teServices.ShowCommitDetails(oid); + } + + void DoOpenOnGitHub() + { + visualStudioBrowser.OpenUrl(WebUrl); + } + + void TimelineItemRemoved(IViewModel item) + { + (item as IDisposable)?.Dispose(); + } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs index 05f70c788c..fa65012187 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/GitHubPaneViewModel.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.Composition; using System.ComponentModel.Design; +using System.Globalization; using System.Linq; using System.Reactive; using System.Reactive.Linq; @@ -35,6 +36,7 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I static readonly Regex pullUri = CreateRoute("/:owner/:repo/pull/:number"); static readonly Regex pullNewReviewUri = CreateRoute("/:owner/:repo/pull/:number/review/new"); static readonly Regex pullUserReviewsUri = CreateRoute("/:owner/:repo/pull/:number/reviews/:login"); + static readonly Regex pullCheckRunsUri = CreateRoute("/:owner/:repo/pull/:number/checkruns/:id"); readonly IViewViewModelFactory viewModelFactory; readonly ISimpleApiClientFactory apiClientFactory; @@ -44,6 +46,7 @@ public sealed class GitHubPaneViewModel : ViewModelBase, IGitHubPaneViewModel, I readonly ILoggedOutViewModel loggedOut; readonly INotAGitHubRepositoryViewModel notAGitHubRepository; readonly INotAGitRepositoryViewModel notAGitRepository; + readonly INoRemoteOriginViewModel noRemoteOrigin; readonly ILoginFailedViewModel loginFailed; readonly SemaphoreSlim navigating = new SemaphoreSlim(1); readonly ObservableAsPropertyHelper contentOverride; @@ -71,6 +74,7 @@ public GitHubPaneViewModel( ILoggedOutViewModel loggedOut, INotAGitHubRepositoryViewModel notAGitHubRepository, INotAGitRepositoryViewModel notAGitRepository, + INoRemoteOriginViewModel noRemoteOrigin, ILoginFailedViewModel loginFailed) { Guard.ArgumentNotNull(viewModelFactory, nameof(viewModelFactory)); @@ -83,6 +87,7 @@ public GitHubPaneViewModel( Guard.ArgumentNotNull(loggedOut, nameof(loggedOut)); Guard.ArgumentNotNull(notAGitHubRepository, nameof(notAGitHubRepository)); Guard.ArgumentNotNull(notAGitRepository, nameof(notAGitRepository)); + Guard.ArgumentNotNull(noRemoteOrigin, nameof(noRemoteOrigin)); Guard.ArgumentNotNull(loginFailed, nameof(loginFailed)); this.viewModelFactory = viewModelFactory; @@ -93,6 +98,7 @@ public GitHubPaneViewModel( this.loggedOut = loggedOut; this.notAGitHubRepository = notAGitHubRepository; this.notAGitRepository = notAGitRepository; + this.noRemoteOrigin = noRemoteOrigin; this.loginFailed = loginFailed; var contentAndNavigatorContent = Observable.CombineLatest( @@ -154,6 +160,14 @@ public GitHubPaneViewModel( }, currentPage.Select(x => x is IOpenInBrowser)); + BrowseRepository = ReactiveCommand.Create( + () => + { + var url = LocalRepository.CloneUrl.ToRepositoryUrl(); + if (url != null) browser.OpenUrl(url); + }, + currentPage.Select(x => x is IOpenInBrowser)); + help = ReactiveCommand.Create(() => { }); help.Subscribe(_ => { @@ -248,24 +262,33 @@ public async Task NavigateTo(Uri uri) { var owner = match.Groups["owner"].Value; var repo = match.Groups["repo"].Value; - var number = int.Parse(match.Groups["number"].Value); + var number = int.Parse(match.Groups["number"].Value, CultureInfo.InvariantCulture); await ShowPullRequest(owner, repo, number); } else if ((match = pullNewReviewUri.Match(uri.AbsolutePath))?.Success == true) { var owner = match.Groups["owner"].Value; var repo = match.Groups["repo"].Value; - var number = int.Parse(match.Groups["number"].Value); + var number = int.Parse(match.Groups["number"].Value, CultureInfo.InvariantCulture); await ShowPullRequestReviewAuthoring(owner, repo, number); } else if ((match = pullUserReviewsUri.Match(uri.AbsolutePath))?.Success == true) { var owner = match.Groups["owner"].Value; var repo = match.Groups["repo"].Value; - var number = int.Parse(match.Groups["number"].Value); + var number = int.Parse(match.Groups["number"].Value, CultureInfo.InvariantCulture); var login = match.Groups["login"].Value; await ShowPullRequestReviews(owner, repo, number, login); } + else if ((match = pullCheckRunsUri.Match(uri.AbsolutePath))?.Success == true) + { + var owner = match.Groups["owner"].Value; + var repo = match.Groups["repo"].Value; + var number = int.Parse(match.Groups["number"].Value, CultureInfo.InvariantCulture); + var id = match.Groups["id"].Value; + + await ShowPullRequestCheckRun(owner, repo, number, id); + } else { throw new NotSupportedException("Unrecognised GitHub pane URL: " + uri.AbsolutePath); @@ -319,6 +342,20 @@ public Task ShowPullRequestReviews(string owner, string repo, int number, string x.User.Login == login); } + /// + public Task ShowPullRequestCheckRun(string owner, string repo, int number, string checkRunId) + { + Guard.ArgumentNotNull(owner, nameof(owner)); + Guard.ArgumentNotNull(repo, nameof(repo)); + + return NavigateTo( + x => x.InitializeAsync(LocalRepository, Connection, owner, repo, number, checkRunId), + x => x.RemoteRepositoryOwner == owner && + x.LocalRepository.Name == repo && + x.PullRequestNumber == number && + x.CheckRunId == checkRunId); + } + /// public Task ShowPullRequestReviewAuthoring(string owner, string repo, int number) { @@ -409,8 +446,17 @@ async Task UpdateContent(LocalRepositoryModel repository) } else if (string.IsNullOrWhiteSpace(repository.CloneUrl)) { - log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl); - Content = notAGitHubRepository; + if (repository.HasRemotesButNoOrigin) + { + log.Debug("No origin remote"); + Content = noRemoteOrigin; + } + else + { + log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl); + Content = notAGitHubRepository; + } + return; } @@ -466,7 +512,7 @@ async Task UpdateContent(LocalRepositoryModel repository) Content = loggedOut; } } - + if (notGitHubRepo) { log.Debug("Not a GitHub repository: {CloneUrl}", repository?.CloneUrl); @@ -489,9 +535,11 @@ static async Task IsValidRepository(ISimpleApiClient client) static Regex CreateRoute(string route) { - // Build RegEx from route (:foo to named group (?[\w_.-]+)). - var routeFormat = "^" + new Regex("(:([a-z]+))\\b").Replace(route, @"(?<$2>[\w_.-]+)") + "$"; + // Build RegEx from route (:foo to named group (?[\w_.\-=]+)). + var routeFormat = "^" + new Regex("(:([a-z]+))\\b").Replace(route, @"(?<$2>[\w_.\-=]+)") + "$"; return new Regex(routeFormat, RegexOptions.ExplicitCapture | RegexOptions.IgnoreCase); } + + public ReactiveCommand BrowseRepository { get; } } } diff --git a/src/GitHub.App/ViewModels/GitHubPane/IssueListViewModelBase.cs b/src/GitHub.App/ViewModels/GitHubPane/IssueListViewModelBase.cs index e7e1007043..271b88ab32 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/IssueListViewModelBase.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/IssueListViewModelBase.cs @@ -7,7 +7,6 @@ using System.Reactive.Disposables; using System.Reactive.Linq; using System.Threading.Tasks; -using System.Windows.Threading; using GitHub.Collections; using GitHub.Extensions; using GitHub.Extensions.Reactive; @@ -153,21 +152,21 @@ public async Task InitializeAsync(LocalRepositoryModel repository, IConnection c Forks = new RepositoryModel[] { - RemoteRepository, - repository, + RemoteRepository, + repository, }; } this.WhenAnyValue(x => x.SelectedState, x => x.RemoteRepository) .Skip(1) - .Subscribe(_ => Refresh().Forget()); + .Subscribe(_ => InitializeItemSource(false).Forget()); Observable.Merge( this.WhenAnyValue(x => x.SearchQuery).Skip(1).SelectUnit(), AuthorFilter.WhenAnyValue(x => x.Selected).Skip(1).SelectUnit()) .Subscribe(_ => FilterChanged()); - await Refresh(); + await InitializeItemSource(true); } catch (Exception ex) { @@ -181,18 +180,43 @@ public async Task InitializeAsync(LocalRepositoryModel repository, IConnection c /// Refreshes the view model. /// /// A task tracking the operation. - public override Task Refresh() + public override Task Refresh() => InitializeItemSource(true); + + /// + /// When overridden in a derived class, creates the + /// that will act as the source for . + /// + /// + /// Whether the item source is being created due to being called. + /// + protected abstract Task> CreateItemSource(bool refresh); + + /// + /// When overridden in a derived class, navigates to the specified item. + /// + /// The item. + /// A task tracking the operation. + protected abstract Task DoOpenItem(IIssueListItemViewModelBase item); + + /// + /// Loads a page of authors for the . + /// + /// The GraphQL "after" cursor. + /// A task that returns a page of authors. + protected abstract Task> LoadAuthors(string after); + + async Task InitializeItemSource(bool refresh) { if (RemoteRepository == null) { // If an exception occurred reading the parent repository, do nothing. - return Task.CompletedTask; + return; } subscription?.Dispose(); var dispose = new CompositeDisposable(); - var itemSource = CreateItemSource(); + var itemSource = await CreateItemSource(refresh); var items = new VirtualizingList(itemSource, null); var view = new VirtualizingListCollectionView(items); @@ -218,30 +242,8 @@ public override Task Refresh() x => items.InitializationError -= x) .Subscribe(x => Error = x.EventArgs.GetException())); subscription = dispose; - - return Task.CompletedTask; } - /// - /// When overridden in a derived class, creates the - /// that will act as the source for . - /// - protected abstract IVirtualizingListSource CreateItemSource(); - - /// - /// When overridden in a derived class, navigates to the specified item. - /// - /// The item. - /// A task tracking the operation. - protected abstract Task DoOpenItem(IIssueListItemViewModelBase item); - - /// - /// Loads a page of authors for the . - /// - /// The GraphQL "after" cursor. - /// A task that returns a page of authors. - protected abstract Task> LoadAuthors(string after); - void FilterChanged() { if (!string.IsNullOrWhiteSpace(SearchQuery)) @@ -252,7 +254,7 @@ void FilterChanged() if (numberFilter == 0) { - stringFilter = SearchQuery.ToUpper(); + stringFilter = SearchQuery.ToUpperInvariant(); } } else @@ -280,7 +282,7 @@ bool FilterItem(object o) } else { - result = item.Title.ToUpper().Contains(stringFilter); + result = item.Title.ToUpperInvariant().Contains(stringFilter); } } } diff --git a/src/GitHub.App/ViewModels/GitHubPane/NoRemoteOriginViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/NoRemoteOriginViewModel.cs new file mode 100644 index 0000000000..7803d272e2 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/NoRemoteOriginViewModel.cs @@ -0,0 +1,28 @@ +using System; +using System.Reactive; +using System.Threading.Tasks; +using System.ComponentModel.Composition; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// The view model for the "No Origin Remote" view in the GitHub pane. + /// + [Export(typeof(INoRemoteOriginViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class NoRemoteOriginViewModel : PanePageViewModelBase, INoRemoteOriginViewModel + { + ITeamExplorerServices teamExplorerServices; + + [ImportingConstructor] + public NoRemoteOriginViewModel(ITeamExplorerServices teamExplorerServices) + { + this.teamExplorerServices = teamExplorerServices; + EditRemotes = ReactiveCommand.CreateFromTask(teamExplorerServices.ShowRepositorySettingsRemotesAsync); + } + + public ReactiveCommand EditRemotes { get; } + } +} diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestAnnotationItemViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestAnnotationItemViewModel.cs new file mode 100644 index 0000000000..d07c806f89 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestAnnotationItemViewModel.cs @@ -0,0 +1,56 @@ +using System.Reactive; +using System.Reactive.Linq; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + public class PullRequestAnnotationItemViewModel : ViewModelBase, IPullRequestAnnotationItemViewModel + { + bool isExpanded; + + /// + /// Initializes the . + /// + /// The check run annotation model. + /// A flag that denotes if the annotation is part of the pull request's changes. + /// The check suite model. + /// The pull request session. + /// The pull request editor service. + public PullRequestAnnotationItemViewModel( + CheckRunAnnotationModel annotation, + bool isFileInPullRequest, + CheckSuiteModel checkSuite, + IPullRequestSession session, + IPullRequestEditorService editorService) + { + Annotation = annotation; + IsFileInPullRequest = isFileInPullRequest; + + OpenAnnotation = ReactiveCommand.CreateFromTask( + async _ => await editorService.OpenDiff(session, annotation.Path, checkSuite.HeadSha, annotation.EndLine - 1), + Observable.Return(IsFileInPullRequest)); + } + + /// + public bool IsFileInPullRequest { get; } + + /// + public CheckRunAnnotationModel Annotation { get; } + + /// + public string LineDescription => $"{Annotation.StartLine}:{Annotation.EndLine}"; + + /// + public ReactiveCommand OpenAnnotation { get; } + + /// + public bool IsExpanded + { + get { return isExpanded; } + set { this.RaiseAndSetIfChanged(ref isExpanded, value); } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestAnnotationsViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestAnnotationsViewModel.cs new file mode 100644 index 0000000000..fd9fe3ab98 --- /dev/null +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestAnnotationsViewModel.cs @@ -0,0 +1,177 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Models; +using GitHub.Services; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + [Export(typeof(IPullRequestAnnotationsViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class PullRequestAnnotationsViewModel : PanePageViewModelBase, IPullRequestAnnotationsViewModel + { + readonly IPullRequestSessionManager sessionManager; + readonly IPullRequestEditorService pullRequestEditorService; + readonly IUsageTracker usageTracker; + + IPullRequestSession session; + string title; + string checkSuiteName; + string checkRunName; + IReadOnlyDictionary annotationsDictionary; + string checkRunSummary; + string checkRunText; + + /// + /// Initializes a new instance of the class. + /// + /// The pull request session manager. + /// The pull request editor service. + [ImportingConstructor] + public PullRequestAnnotationsViewModel( + IPullRequestSessionManager sessionManager, + IPullRequestEditorService pullRequestEditorService, + IUsageTracker usageTracker) + { + this.sessionManager = sessionManager; + this.pullRequestEditorService = pullRequestEditorService; + this.usageTracker = usageTracker; + + NavigateToPullRequest = ReactiveCommand.Create(() => { + NavigateTo(FormattableString.Invariant( + $"{LocalRepository.Owner}/{LocalRepository.Name}/pull/{PullRequestNumber}")); + }); + } + + /// + public async Task InitializeAsync(LocalRepositoryModel localRepository, IConnection connection, string owner, + string repo, int pullRequestNumber, string checkRunId) + { + if (repo != localRepository.Name) + { + throw new NotSupportedException(); + } + + IsLoading = true; + + try + { + LocalRepository = localRepository; + RemoteRepositoryOwner = owner; + PullRequestNumber = pullRequestNumber; + CheckRunId = checkRunId; + session = await sessionManager.GetSession(owner, repo, pullRequestNumber); + Load(session.PullRequest); + } + finally + { + IsLoading = false; + } + } + + /// + public LocalRepositoryModel LocalRepository { get; private set; } + + /// + public string RemoteRepositoryOwner { get; private set; } + + /// + public int PullRequestNumber { get; private set; } + + /// + public string CheckRunId { get; private set; } + + /// + public ReactiveCommand NavigateToPullRequest { get; private set; } + + /// + public string PullRequestTitle + { + get { return title; } + private set { this.RaiseAndSetIfChanged(ref title, value); } + } + + /// + public string CheckSuiteName + { + get { return checkSuiteName; } + private set { this.RaiseAndSetIfChanged(ref checkSuiteName, value); } + } + + /// + public string CheckRunName + { + get { return checkRunName; } + private set { this.RaiseAndSetIfChanged(ref checkRunName, value); } + } + + /// + public string CheckRunSummary + { + get { return checkRunSummary; } + private set { this.RaiseAndSetIfChanged(ref checkRunSummary, value); } + } + + /// + public string CheckRunText + { + get { return checkRunText; } + private set { this.RaiseAndSetIfChanged(ref checkRunText, value); } + } + + /// + public IReadOnlyDictionary AnnotationsDictionary + { + get { return annotationsDictionary; } + private set { this.RaiseAndSetIfChanged(ref annotationsDictionary, value); } + } + + void Load(PullRequestDetailModel pullRequest) + { + IsBusy = true; + + try + { + PullRequestTitle = pullRequest.Title; + + var checkSuiteRun = pullRequest + .CheckSuites.SelectMany(checkSuite => checkSuite.CheckRuns + .Select(checkRun => new{checkSuite, checkRun})) + .First(arg => arg.checkRun.Id == CheckRunId); + + CheckSuiteName = checkSuiteRun.checkSuite.ApplicationName; + CheckRunName = checkSuiteRun.checkRun.Name; + CheckRunSummary = checkSuiteRun.checkRun.Summary; + CheckRunText = checkSuiteRun.checkRun.Text; + + var changedFiles = new HashSet(session.PullRequest.ChangedFiles.Select(model => model.FileName)); + + var annotationsLookup = checkSuiteRun.checkRun.Annotations + .ToLookup(annotation => annotation.Path); + + AnnotationsDictionary = annotationsLookup + .Select(models => models.Key) + .OrderBy(s => s) + .ToDictionary( + path => path, + path => annotationsLookup[path] + .Select(annotation => new PullRequestAnnotationItemViewModel(annotation, changedFiles.Contains(path), checkSuiteRun.checkSuite, session, pullRequestEditorService)) + .Cast() + .ToArray() + ); + + usageTracker.IncrementCounter(x => x.NumberOfPullRequestOpenAnnotationsList).Forget(); + } + finally + { + IsBusy = false; + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs index f95bd95fd1..a5cdc09851 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckViewModel.cs @@ -4,29 +4,33 @@ using System.Linq; using System.Linq.Expressions; using System.Reactive; -using System.Reactive.Linq; -using System.Windows.Media.Imaging; using GitHub.Extensions; using GitHub.Factories; using GitHub.Models; +using GitHub.Primitives; using GitHub.Services; using ReactiveUI; namespace GitHub.ViewModels.GitHubPane { + /// [Export(typeof(IPullRequestCheckViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] public class PullRequestCheckViewModel: ViewModelBase, IPullRequestCheckViewModel { private readonly IUsageTracker usageTracker; - const string DefaultAvatar = "pack://application:,,,/GitHub.App;component/Images/default_user_avatar.png"; + /// + /// Factory method to create a . + /// + /// A viewviewmodel factory. + /// The pull request. public static IEnumerable Build(IViewViewModelFactory viewViewModelFactory, PullRequestDetailModel pullRequest) { - var statuses = pullRequest.Statuses?.Select(model => + var statuses = pullRequest.Statuses?.Select(statusModel => { PullRequestCheckStatus checkStatus; - switch (model.State) + switch (statusModel.State) { case StatusState.Expected: case StatusState.Error: @@ -45,19 +49,22 @@ public static IEnumerable Build(IViewViewModelFactor var pullRequestCheckViewModel = (PullRequestCheckViewModel) viewViewModelFactory.CreateViewModel(); pullRequestCheckViewModel.CheckType = PullRequestCheckType.StatusApi; - pullRequestCheckViewModel.Title = model.Context; - pullRequestCheckViewModel.Description = model.Description; + pullRequestCheckViewModel.Title = statusModel.Context; + pullRequestCheckViewModel.Description = statusModel.Description; pullRequestCheckViewModel.Status = checkStatus; - pullRequestCheckViewModel.DetailsUrl = !string.IsNullOrEmpty(model.TargetUrl) ? new Uri(model.TargetUrl) : null; + pullRequestCheckViewModel.DetailsUrl = !string.IsNullOrEmpty(statusModel.TargetUrl) ? new Uri(statusModel.TargetUrl) : null; return pullRequestCheckViewModel; }) ?? Array.Empty(); - var checks = pullRequest.CheckSuites?.SelectMany(model => model.CheckRuns) - .Select(model => + var checks = + pullRequest.CheckSuites? + .SelectMany(checkSuite => checkSuite.CheckRuns + .Select(checkRun => new { checkSuiteModel = checkSuite, checkRun})) + .Select(arg => { PullRequestCheckStatus checkStatus; - switch (model.Status) + switch (arg.checkRun.Status) { case CheckStatusState.Requested: case CheckStatusState.Queued: @@ -66,7 +73,7 @@ public static IEnumerable Build(IViewViewModelFactor break; case CheckStatusState.Completed: - switch (model.Conclusion) + switch (arg.checkRun.Conclusion) { case CheckConclusionState.Success: checkStatus = PullRequestCheckStatus.Success; @@ -91,17 +98,22 @@ public static IEnumerable Build(IViewViewModelFactor var pullRequestCheckViewModel = (PullRequestCheckViewModel)viewViewModelFactory.CreateViewModel(); pullRequestCheckViewModel.CheckType = PullRequestCheckType.ChecksApi; - pullRequestCheckViewModel.Title = model.Name; - pullRequestCheckViewModel.Description = model.Summary; + pullRequestCheckViewModel.CheckRunId = arg.checkRun.Id; + pullRequestCheckViewModel.HasAnnotations = arg.checkRun.Annotations?.Any() ?? false; + pullRequestCheckViewModel.Title = arg.checkRun.Name; + pullRequestCheckViewModel.Description = arg.checkRun.Summary; pullRequestCheckViewModel.Status = checkStatus; - pullRequestCheckViewModel.DetailsUrl = new Uri(model.DetailsUrl); - + pullRequestCheckViewModel.DetailsUrl = new Uri(arg.checkRun.DetailsUrl); return pullRequestCheckViewModel; }) ?? Array.Empty(); return statuses.Concat(checks).OrderBy(model => model.Title); } + /// + /// Initializes a new instance of . + /// + /// The usage tracker. [ImportingConstructor] public PullRequestCheckViewModel(IUsageTracker usageTracker) { @@ -124,16 +136,28 @@ private void DoOpenDetailsUrl() usageTracker.IncrementCounter(expression).Forget(); } + /// public string Title { get; private set; } + /// public string Description { get; private set; } + /// public PullRequestCheckType CheckType { get; private set; } + /// + public string CheckRunId { get; private set; } + + /// + public bool HasAnnotations { get; private set; } + + /// public PullRequestCheckStatus Status{ get; private set; } + /// public Uri DetailsUrl { get; private set; } + /// public ReactiveCommand OpenDetailsUrl { get; } } } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCreationViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCreationViewModel.cs index 89f8e37c50..bd25c85c52 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCreationViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestCreationViewModel.cs @@ -18,6 +18,7 @@ using GitHub.Models.Drafts; using GitHub.Primitives; using GitHub.Services; +using GitHub.UI; using GitHub.Validation; using Octokit; using ReactiveUI; @@ -51,8 +52,9 @@ public PullRequestCreationViewModel( IPullRequestService service, INotificationService notifications, IMessageDraftStore draftStore, - IGitService gitService) - : this(modelServiceFactory, service, notifications, draftStore, gitService, DefaultScheduler.Instance) + IGitService gitService, + IAutoCompleteAdvisor autoCompleteAdvisor) + : this(modelServiceFactory, service, notifications, draftStore, gitService, autoCompleteAdvisor, DefaultScheduler.Instance) { } @@ -62,6 +64,7 @@ public PullRequestCreationViewModel( INotificationService notifications, IMessageDraftStore draftStore, IGitService gitService, + IAutoCompleteAdvisor autoCompleteAdvisor, IScheduler timerScheduler) { Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); @@ -69,12 +72,14 @@ public PullRequestCreationViewModel( Guard.ArgumentNotNull(notifications, nameof(notifications)); Guard.ArgumentNotNull(draftStore, nameof(draftStore)); Guard.ArgumentNotNull(gitService, nameof(gitService)); + Guard.ArgumentNotNull(autoCompleteAdvisor, nameof(autoCompleteAdvisor)); Guard.ArgumentNotNull(timerScheduler, nameof(timerScheduler)); this.service = service; this.modelServiceFactory = modelServiceFactory; this.draftStore = draftStore; this.gitService = gitService; + this.AutoCompleteAdvisor = autoCompleteAdvisor; this.timerScheduler = timerScheduler; this.WhenAnyValue(x => x.Branches) @@ -336,6 +341,7 @@ protected string GetDraftKey() public RemoteRepositoryModel GitHubRepository { get { return githubRepository?.Value; } } bool IsExecuting { get { return isExecuting.Value; } } + public IAutoCompleteAdvisor AutoCompleteAdvisor { get; } bool initialized; bool Initialized diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs index f968b391c3..4c8cb4c620 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDetailViewModel.cs @@ -7,28 +7,33 @@ using System.Reactive; using System.Reactive.Linq; using System.Reactive.Threading.Tasks; +using System.Threading; using System.Threading.Tasks; using GitHub.App; using GitHub.Commands; using GitHub.Extensions; using GitHub.Factories; -using GitHub.Helpers; using GitHub.Logging; using GitHub.Models; using GitHub.Services; using LibGit2Sharp; +using Microsoft.VisualStudio.StaticReviews.Contracts; using ReactiveUI; +using ReactiveUI.Legacy; using Serilog; using static System.FormattableString; +using ReactiveCommand = ReactiveUI.ReactiveCommand; +using GitHub.Primitives; +using Microsoft.VisualStudio.Threading; +using Microsoft.VisualStudio.Shell; +using Task = System.Threading.Tasks.Task; namespace GitHub.ViewModels.GitHubPane { - /// - /// A view model which displays the details of a pull request. - /// + /// [Export(typeof(IPullRequestDetailViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] - public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullRequestDetailViewModel + public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullRequestDetailViewModel, IStaticReviewFileMap { static readonly ILogger log = LogManager.ForContext(); @@ -40,6 +45,8 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq readonly ISyncSubmodulesCommand syncSubmodulesCommand; readonly IViewViewModelFactory viewViewModelFactory; readonly IGitService gitService; + readonly IOpenIssueishDocumentCommand openDocumentCommand; + IModelService modelService; PullRequestDetailModel model; IActorViewModel author; @@ -57,7 +64,7 @@ public sealed class PullRequestDetailViewModel : PanePageViewModelBase, IPullReq bool refreshOnActivate; Uri webUrl; IDisposable sessionSubscription; - IReadOnlyList checks; + IReadOnlyList checks = Array.Empty(); /// /// Initializes a new instance of the class. @@ -79,7 +86,9 @@ public PullRequestDetailViewModel( IPullRequestFilesViewModel files, ISyncSubmodulesCommand syncSubmodulesCommand, IViewViewModelFactory viewViewModelFactory, - IGitService gitService) + IGitService gitService, + IOpenIssueishDocumentCommand openDocumentCommand, + [Import(AllowDefault = true)] JoinableTaskContext joinableTaskContext) { Guard.ArgumentNotNull(pullRequestsService, nameof(pullRequestsService)); Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); @@ -89,6 +98,7 @@ public PullRequestDetailViewModel( Guard.ArgumentNotNull(syncSubmodulesCommand, nameof(syncSubmodulesCommand)); Guard.ArgumentNotNull(viewViewModelFactory, nameof(viewViewModelFactory)); Guard.ArgumentNotNull(gitService, nameof(gitService)); + Guard.ArgumentNotNull(openDocumentCommand, nameof(openDocumentCommand)); this.pullRequestsService = pullRequestsService; this.sessionManager = sessionManager; @@ -98,6 +108,9 @@ public PullRequestDetailViewModel( this.syncSubmodulesCommand = syncSubmodulesCommand; this.viewViewModelFactory = viewViewModelFactory; this.gitService = gitService; + this.openDocumentCommand = openDocumentCommand; + JoinableTaskContext = joinableTaskContext ?? ThreadHelper.JoinableTaskContext; + Files = files; Checkout = ReactiveCommand.CreateFromObservable( @@ -130,18 +143,19 @@ public PullRequestDetailViewModel( SyncSubmodules.Subscribe(_ => Refresh().ToObservable()); SubscribeOperationError(SyncSubmodules); + OpenConversation = ReactiveCommand.Create(DoOpenConversation); + OpenOnGitHub = ReactiveCommand.Create(DoOpenDetailsUrl); + ShowReview = ReactiveCommand.Create(DoShowReview); - } - private void DoOpenDetailsUrl() - { - usageTracker.IncrementCounter(measuresModel => measuresModel.NumberOfPRDetailsOpenInGitHub).Forget(); + ShowAnnotations = ReactiveCommand.Create(DoShowAnnotations); } - /// - /// Gets the underlying pull request model. - /// + [Import(AllowDefault = true)] + private IStaticReviewFileMapManager StaticReviewFileMapManager { get; set; } + + /// public PullRequestDetailModel Model { get { return model; } @@ -159,124 +173,89 @@ private set } } - /// - /// Gets the local repository. - /// + /// public LocalRepositoryModel LocalRepository { get; private set; } - /// - /// Gets the owner of the remote repository that contains the pull request. - /// - /// - /// The remote repository may be different from the local repository if the local - /// repository is a fork and the user is viewing pull requests from the parent repository. - /// + /// public string RemoteRepositoryOwner { get; private set; } - /// - /// Gets the Pull Request number. - /// + /// public int Number { get; private set; } - /// - /// Gets the Pull Request author. - /// + /// public IActorViewModel Author { get { return author; } private set { this.RaiseAndSetIfChanged(ref author, value); } } - /// - /// Gets the session for the pull request. - /// + /// public IPullRequestSession Session { get; private set; } - /// - /// Gets a string describing how to display the pull request's source branch. - /// + /// public string SourceBranchDisplayName { get { return sourceBranchDisplayName; } private set { this.RaiseAndSetIfChanged(ref sourceBranchDisplayName, value); } } - /// - /// Gets a string describing how to display the pull request's target branch. - /// + /// public string TargetBranchDisplayName { get { return targetBranchDisplayName; } private set { this.RaiseAndSetIfChanged(ref targetBranchDisplayName, value); } } - /// - /// Gets a value indicating whether the pull request branch is checked out. - /// + /// public bool IsCheckedOut { get { return isCheckedOut; } private set { this.RaiseAndSetIfChanged(ref isCheckedOut, value); } } - /// - /// Gets a value indicating whether the pull request comes from a fork. - /// + /// public bool IsFromFork { get { return isFromFork; } private set { this.RaiseAndSetIfChanged(ref isFromFork, value); } } - /// - /// Gets the pull request body. - /// + /// public string Body { get { return body; } private set { this.RaiseAndSetIfChanged(ref body, value); } } - /// - /// Gets the state associated with the command. - /// + /// public IPullRequestCheckoutState CheckoutState { get { return checkoutState; } private set { this.RaiseAndSetIfChanged(ref checkoutState, value); } } - /// - /// Gets the state associated with the and commands. - /// + /// public IPullRequestUpdateState UpdateState { get { return updateState; } private set { this.RaiseAndSetIfChanged(ref updateState, value); } } - /// - /// Gets the error message to be displayed in the action area as a result of an error in a - /// git operation. - /// + /// public string OperationError { get { return operationError; } private set { this.RaiseAndSetIfChanged(ref operationError, value); } } - /// - /// Gets the latest pull request review for each user. - /// + /// public IReadOnlyList Reviews { get { return reviews; } private set { this.RaiseAndSetIfChanged(ref reviews, value); } } - /// - /// Gets the pull request's changed files. - /// + /// public IPullRequestFilesViewModel Files { get; } /// @@ -288,50 +267,38 @@ public Uri WebUrl private set { this.RaiseAndSetIfChanged(ref webUrl, value); } } - /// - /// Gets a command that checks out the pull request locally. - /// + /// public ReactiveCommand Checkout { get; } - /// - /// Gets a command that pulls changes to the current branch. - /// + /// public ReactiveCommand Pull { get; } - /// - /// Gets a command that pushes changes from the current branch. - /// + /// public ReactiveCommand Push { get; } - /// - /// Sync submodules for PR branch. - /// + /// public ReactiveCommand SyncSubmodules { get; } - /// - /// Gets a command that opens the pull request on GitHub. - /// + /// + public ReactiveCommand OpenConversation { get; } + + /// public ReactiveCommand OpenOnGitHub { get; } - /// - /// Gets a command that navigates to a pull request review. - /// + /// public ReactiveCommand ShowReview { get; } + /// + public ReactiveCommand ShowAnnotations { get; } + + /// public IReadOnlyList Checks { get { return checks; } private set { this.RaiseAndSetIfChanged(ref checks, value); } } - /// - /// Initializes the view model. - /// - /// The local repository. - /// The connection to the repository host. - /// The pull request's repository owner. - /// The pull request's repository name. - /// The pull request number. + /// public async Task InitializeAsync( LocalRepositoryModel localRepository, IConnection connection, @@ -399,9 +366,10 @@ public async Task Load(PullRequestDetailModel pullRequest) Body = !string.IsNullOrWhiteSpace(pullRequest.Body) ? pullRequest.Body : Resources.NoDescriptionProvidedMarkdown; Reviews = PullRequestReviewSummaryViewModel.BuildByUser(Session.User, pullRequest).ToList(); - Checks = PullRequestCheckViewModel.Build(viewViewModelFactory, pullRequest)?.ToList(); + Checks = (IReadOnlyList)PullRequestCheckViewModel.Build(viewViewModelFactory, pullRequest)?.ToList() ?? Array.Empty(); - await Files.InitializeAsync(Session); + // Only show unresolved comments + await Files.InitializeAsync(Session, c => !c.IsResolved); var localBranches = await pullRequestsService.GetLocalBranches(LocalRepository, pullRequest).ToList(); @@ -419,6 +387,7 @@ public async Task Load(PullRequestDetailModel pullRequest) if (pullEnabled) { pullToolTip = string.Format( + CultureInfo.InvariantCulture, Resources.PullRequestDetailsPullToolTip, IsFromFork ? Resources.Fork : Resources.Remote, SourceBranchDisplayName); @@ -431,6 +400,7 @@ public async Task Load(PullRequestDetailModel pullRequest) if (pushEnabled) { pushToolTip = string.Format( + CultureInfo.InvariantCulture, Resources.PullRequestDetailsPushToolTip, IsFromFork ? Resources.Fork : Resources.Remote, SourceBranchDisplayName); @@ -445,7 +415,7 @@ public async Task Load(PullRequestDetailModel pullRequest) } var submodulesToSync = await pullRequestsService.CountSubmodulesToSync(LocalRepository); - var syncSubmodulesToolTip = string.Format(Resources.SyncSubmodules, submodulesToSync); + var syncSubmodulesToolTip = string.Format(CultureInfo.InvariantCulture, Resources.SyncSubmodules, submodulesToSync); UpdateState = new UpdateCommandState(divergence, pullEnabled, pushEnabled, pullToolTip, pushToolTip, syncSubmodulesToolTip, submodulesToSync); CheckoutState = null; @@ -453,8 +423,14 @@ public async Task Load(PullRequestDetailModel pullRequest) else { var caption = localBranches.Count > 0 ? - string.Format(Resources.PullRequestDetailsCheckout, localBranches.First().DisplayName) : - string.Format(Resources.PullRequestDetailsCheckoutTo, await pullRequestsService.GetDefaultLocalBranchName(LocalRepository, Model.Number, Model.Title)); + string.Format( + CultureInfo.InvariantCulture, + Resources.PullRequestDetailsCheckout, + localBranches.First().DisplayName) : + string.Format( + CultureInfo.InvariantCulture, + Resources.PullRequestDetailsCheckoutTo, + await pullRequestsService.GetDefaultLocalBranchName(LocalRepository, Model.Number, Model.Title)); var clean = await pullRequestsService.IsWorkingDirectoryClean(LocalRepository); string disabled = null; @@ -499,7 +475,7 @@ public override async Task Refresh() { try { - await ThreadingHelper.SwitchToMainThreadAsync(); + await JoinableTaskContext.Factory.SwitchToMainThreadAsync(); Error = null; OperationError = null; @@ -521,11 +497,7 @@ public override async Task Refresh() } } - /// - /// Gets the full path to a file in the working directory. - /// - /// The file. - /// The full path to the file in the working directory. + /// public string GetLocalFilePath(IPullRequestFileNode file) { return Path.Combine(LocalRepository.LocalPath, file.RelativePath); @@ -535,6 +507,7 @@ public string GetLocalFilePath(IPullRequestFileNode file) public override void Activated() { active = true; + this.StaticReviewFileMapManager?.RegisterStaticReviewFileMap(this); if (refreshOnActivate) { @@ -544,7 +517,43 @@ public override void Activated() } /// - public override void Deactivated() => active = false; + public override void Deactivated() + { + this.StaticReviewFileMapManager?.UnregisterStaticReviewFileMap(this); + active = false; + } + + /// + public Task GetLocalPathFromObjectishAsync(string objectish, CancellationToken cancellationToken) + { + if (this.pullRequestsService != null) + { + string commitId = objectish.Substring(0, objectish.IndexOf(':')); + string relativePath = objectish.Substring(objectish.IndexOf(':')+1).TrimStart('/'); + + return this.pullRequestsService.ExtractToTempFile( + this.Session.LocalRepository, + this.Session.PullRequest, + relativePath, + commitId, + this.pullRequestsService.GetEncoding(this.Session.LocalRepository, relativePath)); + } + + return Task.FromResult(null); + } + + /// + public Task GetObjectishFromLocalPathAsync(string localPath, CancellationToken cancellationToken) + { + // We rely on pull request service's global map here instead of trying to get it from IPullRequestSessionManager via ITextBuffer + // because it is possible that the file queried wasn't opened by GitHub extension and instead was opened by LSP + if (this.pullRequestsService is IStaticReviewFileMap staticReviewFileMap) + { + return staticReviewFileMap.GetObjectishFromLocalPathAsync(localPath, cancellationToken); + } + + return Task.FromResult(null); + } /// protected override void Dispose(bool disposing) @@ -651,10 +660,23 @@ async Task DoSyncSubmodules() } } - void DoShowReview(IPullRequestReviewSummaryViewModel item) + void DoOpenConversation() { - var review = item; + var p = new OpenIssueishParams( + HostAddress.Create(LocalRepository.CloneUrl), + RemoteRepositoryOwner, + LocalRepository.Name, + Number); + openDocumentCommand.Execute(p); + } + void DoOpenDetailsUrl() + { + usageTracker.IncrementCounter(measuresModel => measuresModel.NumberOfPRDetailsOpenInGitHub).Forget(); + } + + void DoShowReview(IPullRequestReviewSummaryViewModel review) + { if (review.State == PullRequestReviewState.Pending) { NavigateTo(Invariant($"{RemoteRepositoryOwner}/{LocalRepository.Name}/pull/{Number}/review/new")); @@ -665,6 +687,11 @@ void DoShowReview(IPullRequestReviewSummaryViewModel item) } } + void DoShowAnnotations(IPullRequestCheckViewModel checkView) + { + NavigateTo(Invariant($"{RemoteRepositoryOwner}/{LocalRepository.Name}/pull/{Number}/checkruns/{checkView.CheckRunId}")); + } + class CheckoutCommandState : IPullRequestCheckoutState { public CheckoutCommandState(string caption, string disabledMessage) @@ -711,5 +738,7 @@ public UpdateCommandState( public string SyncSubmodulesToolTip { get; } public int SubmodulesToSync { get; } } + + JoinableTaskContext JoinableTaskContext { get; } } } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs index c270780ff8..bd79e0270e 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestDirectoryNode.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using GitHub.Primitives; namespace GitHub.ViewModels.GitHubPane { @@ -12,11 +13,11 @@ public class PullRequestDirectoryNode : IPullRequestDirectoryNode /// /// Initializes a new instance of the class. /// - /// The path to the directory, relative to the repository. - public PullRequestDirectoryNode(string relativePath) + /// The path to the directory, relative to the repository. + public PullRequestDirectoryNode(string relativeOrGitPath) { - DirectoryName = System.IO.Path.GetFileName(relativePath); - RelativePath = relativePath.Replace("/", "\\"); + DirectoryName = System.IO.Path.GetFileName(relativeOrGitPath); + RelativePath = Paths.ToWindowsPath(relativeOrGitPath); Directories = new List(); Files = new List(); } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs index f9e1e4e164..faf3a7fdb7 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFileNode.cs @@ -3,6 +3,7 @@ using GitHub.App; using GitHub.Extensions; using GitHub.Models; +using GitHub.Primitives; using ReactiveUI; namespace GitHub.ViewModels.GitHubPane @@ -13,12 +14,15 @@ namespace GitHub.ViewModels.GitHubPane public class PullRequestFileNode : ReactiveObject, IPullRequestFileNode { int commentCount; + int annotationNoticeCount; + int annotationWarningCount; + int _annotationFailureCount; /// /// Initializes a new instance of the class. /// /// The absolute path to the repository. - /// The path to the file, relative to the repository. + /// The path to the file, relative to the repository. /// The SHA of the file. /// The way the file was changed. /// The string to display in the [message] box next to the filename. @@ -28,17 +32,17 @@ public class PullRequestFileNode : ReactiveObject, IPullRequestFileNode /// public PullRequestFileNode( string repositoryPath, - string relativePath, + string relativeOrGitPath, string sha, PullRequestFileStatus status, string oldPath) { Guard.ArgumentNotEmptyString(repositoryPath, nameof(repositoryPath)); - Guard.ArgumentNotEmptyString(relativePath, nameof(relativePath)); + Guard.ArgumentNotEmptyString(relativeOrGitPath, nameof(relativeOrGitPath)); Guard.ArgumentNotEmptyString(sha, nameof(sha)); - FileName = Path.GetFileName(relativePath); - RelativePath = relativePath.Replace("/", "\\"); + FileName = Path.GetFileName(relativeOrGitPath); + RelativePath = Paths.ToWindowsPath(relativeOrGitPath); Sha = sha; Status = status; OldPath = oldPath; @@ -51,7 +55,7 @@ public PullRequestFileNode( { if (oldPath != null) { - StatusDisplay = Path.GetDirectoryName(oldPath) == Path.GetDirectoryName(relativePath) ? + StatusDisplay = Path.GetDirectoryName(oldPath) == Path.GetDirectoryName(relativeOrGitPath) ? Path.GetFileName(oldPath) : oldPath; } else @@ -99,5 +103,32 @@ public int CommentCount get { return commentCount; } set { this.RaiseAndSetIfChanged(ref commentCount, value); } } + + /// + /// Gets or sets the number of annotation notices on the file. + /// + public int AnnotationNoticeCount + { + get { return annotationNoticeCount; } + set { this.RaiseAndSetIfChanged(ref annotationNoticeCount, value); } + } + + /// + /// Gets or sets the number of annotation errors on the file. + /// + public int AnnotationWarningCount + { + get { return annotationWarningCount; } + set { this.RaiseAndSetIfChanged(ref annotationWarningCount, value); } + } + + /// + /// Gets or sets the number of annotation failures on the file. + /// + public int AnnotationFailureCount + { + get { return _annotationFailureCount; } + set { this.RaiseAndSetIfChanged(ref _annotationFailureCount, value); } + } } } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs index f1c51bdf51..9be03eb613 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestFilesViewModel.cs @@ -12,6 +12,7 @@ using System.Windows.Input; using GitHub.Extensions; using GitHub.Models; +using GitHub.Primitives; using GitHub.Services; using LibGit2Sharp; using ReactiveUI; @@ -65,6 +66,29 @@ public PullRequestFilesViewModel( await editorService.OpenDiff(pullRequestSession, file.RelativePath, thread); } }); + + OpenFirstAnnotationNotice = ReactiveCommand.CreateFromTask( + async file => await OpenFirstAnnotation(editorService, file, CheckAnnotationLevel.Notice)); + + OpenFirstAnnotationWarning = ReactiveCommand.CreateFromTask( + async file => await OpenFirstAnnotation(editorService, file, CheckAnnotationLevel.Warning)); + + OpenFirstAnnotationFailure = ReactiveCommand.CreateFromTask( + async file => await OpenFirstAnnotation(editorService, file, CheckAnnotationLevel.Failure)); + } + + private async Task OpenFirstAnnotation(IPullRequestEditorService editorService, IPullRequestFileNode file, + CheckAnnotationLevel checkAnnotationLevel) + { + var annotationModel = await GetFirstAnnotation(file, checkAnnotationLevel); + + if (annotationModel != null) + { + //AnnotationModel.EndLine is a 1-based number + //EditorService.OpenDiff takes a 0-based line number to start searching AFTER and will open the next tag + var nextInlineCommentFromLine = annotationModel.EndLine - 2; + await editorService.OpenDiff(pullRequestSession, file.RelativePath, annotationModel.HeadSha, nextInlineCommentFromLine); + } } /// @@ -122,6 +146,18 @@ public async Task InitializeAsync( { subscriptions.Add(file.WhenAnyValue(x => x.InlineCommentThreads) .Subscribe(x => node.CommentCount = CountComments(x, filter))); + + subscriptions.Add(file.WhenAnyValue(x => x.InlineAnnotations) + .Subscribe(x => + { + var noticeCount = x.Count(model => model.AnnotationLevel == CheckAnnotationLevel.Notice); + var warningCount = x.Count(model => model.AnnotationLevel == CheckAnnotationLevel.Warning); + var failureCount = x.Count(model => model.AnnotationLevel == CheckAnnotationLevel.Failure); + + node.AnnotationNoticeCount = noticeCount; + node.AnnotationWarningCount = warningCount; + node.AnnotationFailureCount = failureCount; + })); } var dir = GetDirectory(Path.GetDirectoryName(node.RelativePath), dirs); @@ -148,6 +184,15 @@ public async Task InitializeAsync( /// public ReactiveCommand OpenFirstComment { get; } + /// + public ReactiveCommand OpenFirstAnnotationNotice { get; } + + /// + public ReactiveCommand OpenFirstAnnotationWarning { get; } + + /// + public ReactiveCommand OpenFirstAnnotationFailure { get; } + static int CountComments( IEnumerable thread, Func commentFilter) @@ -180,8 +225,8 @@ static string GetOldFileName(PullRequestFileModel file, TreeChanges changes) { if (file.Status == PullRequestFileStatus.Renamed) { - var fileName = file.FileName.Replace("/", "\\"); - return changes?.Renamed.FirstOrDefault(x => x.Path == fileName)?.OldPath; + var gitPath = Paths.ToGitPath(file.FileName); + return changes?.Renamed.FirstOrDefault(x => x.Path == gitPath)?.OldPath; } return null; @@ -200,6 +245,15 @@ async Task GetFirstCommentThread(IPullRequestFileNode return threads.FirstOrDefault(); } + async Task GetFirstAnnotation(IPullRequestFileNode file, + CheckAnnotationLevel annotationLevel) + { + var sessionFile = await pullRequestSession.GetFile(file.RelativePath); + var annotations = sessionFile.InlineAnnotations; + + return annotations.OrderBy(model => model.EndLine).FirstOrDefault(model => model.AnnotationLevel == annotationLevel); + } + /// /// Implements the command. /// diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs index b60cf0e70b..30436ed5a0 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListItemViewModel.cs @@ -19,7 +19,10 @@ public PullRequestListItemViewModel(PullRequestListItemModel model) { Id = model.Id; Author = new ActorViewModel(model.Author); - Checks = model.Checks; + ChecksSummary = model.ChecksSummary; + ChecksErrorCount = model.ChecksErrorCount; + ChecksPendingCount = model.ChecksPendingCount; + ChecksSuccessCount = model.ChecksSuccessCount; CommentCount = model.CommentCount; Number = model.Number; Title = model.Title; @@ -33,7 +36,16 @@ public PullRequestListItemViewModel(PullRequestListItemModel model) public IActorViewModel Author { get; } /// - public PullRequestChecksState Checks { get; } + public PullRequestChecksSummaryState ChecksSummary { get; } + + /// + public int ChecksSuccessCount { get; } + + /// + public int ChecksPendingCount { get; } + + /// + public int ChecksErrorCount { get; } /// public int CommentCount { get; } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs index 7468367837..23890674c8 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestListViewModel.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Diagnostics; using System.Reactive; using System.Reactive.Linq; using System.Threading.Tasks; using GitHub.Collections; +using GitHub.Commands; using GitHub.Extensions; using GitHub.Models; using GitHub.Primitives; @@ -68,8 +68,16 @@ public PullRequestListViewModel( public ReactiveCommand OpenItemInBrowser { get; } /// - protected override IVirtualizingListSource CreateItemSource() + protected override async Task> CreateItemSource(bool refresh) { + if (refresh) + { + await service.ClearPullRequestsCache( + HostAddress.Create(RemoteRepository.CloneUrl), + RemoteRepository.Owner, + RemoteRepository.Name); + } + return new ItemSource(this); } @@ -125,18 +133,18 @@ protected override IIssueListItemViewModelBase CreateViewModel(PullRequestListIt protected override async Task> LoadPage(string after) { - PullRequestStateEnum[] states; + PullRequestState[] states; switch (owner.SelectedState) { case "Open": - states = new[] { PullRequestStateEnum.Open }; + states = new[] { PullRequestState.Open }; break; case "Closed": - states = new[] { PullRequestStateEnum.Closed, PullRequestStateEnum.Merged }; + states = new[] { PullRequestState.Closed, PullRequestState.Merged }; break; default: - states = new[] { PullRequestStateEnum.Open, PullRequestStateEnum.Closed, PullRequestStateEnum.Merged }; + states = new[] { PullRequestState.Open, PullRequestState.Closed, PullRequestState.Merged }; break; } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs index c263af4585..82ff54359c 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewAuthoringViewModel.cs @@ -45,8 +45,9 @@ public PullRequestReviewAuthoringViewModel( IPullRequestEditorService editorService, IPullRequestSessionManager sessionManager, IMessageDraftStore draftStore, - IPullRequestFilesViewModel files) - : this(pullRequestService, editorService, sessionManager,draftStore, files, DefaultScheduler.Instance) + IPullRequestFilesViewModel files, + IAutoCompleteAdvisor autoCompleteAdvisor) + : this(pullRequestService, editorService, sessionManager,draftStore, files, autoCompleteAdvisor, DefaultScheduler.Instance) { } @@ -56,12 +57,14 @@ public PullRequestReviewAuthoringViewModel( IPullRequestSessionManager sessionManager, IMessageDraftStore draftStore, IPullRequestFilesViewModel files, + IAutoCompleteAdvisor autoCompleteAdvisor, IScheduler timerScheduler) { Guard.ArgumentNotNull(editorService, nameof(editorService)); Guard.ArgumentNotNull(sessionManager, nameof(sessionManager)); Guard.ArgumentNotNull(draftStore, nameof(draftStore)); Guard.ArgumentNotNull(files, nameof(files)); + Guard.ArgumentNotNull(autoCompleteAdvisor, nameof(autoCompleteAdvisor)); Guard.ArgumentNotNull(timerScheduler, nameof(timerScheduler)); this.pullRequestService = pullRequestService; @@ -77,6 +80,7 @@ public PullRequestReviewAuthoringViewModel( .ToProperty(this, x => x.CanApproveRequestChanges); Files = files; + AutoCompleteAdvisor = autoCompleteAdvisor; var hasBodyOrComments = this.WhenAnyValue( x => x.Body, @@ -118,6 +122,9 @@ public PullRequestDetailModel PullRequestModel /// public IPullRequestFilesViewModel Files { get; } + /// + public IAutoCompleteAdvisor AutoCompleteAdvisor { get; } + /// public string Body { diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewCommentViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewCommentViewModel.cs index 9a9a473161..7c960baaa5 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewCommentViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestReviewCommentViewModel.cs @@ -3,9 +3,11 @@ using System.Reactive; using System.Threading.Tasks; using GitHub.Extensions; +using GitHub.Logging; using GitHub.Models; using GitHub.Services; using ReactiveUI; +using Serilog; namespace GitHub.ViewModels.GitHubPane { @@ -14,6 +16,8 @@ namespace GitHub.ViewModels.GitHubPane /// public class PullRequestReviewCommentViewModel : IPullRequestReviewFileCommentViewModel { + static readonly ILogger log = LogManager.ForContext(); + readonly IPullRequestEditorService editorService; readonly IPullRequestSession session; readonly PullRequestReviewCommentModel model; @@ -52,8 +56,24 @@ async Task DoOpen() { if (thread == null) { - var file = await session.GetFile(RelativePath, model.Thread.CommitSha); - thread = file.InlineCommentThreads.FirstOrDefault(t => t.Comments.Any(c => c.Comment.Id == model.Id)); + if(model.Thread.IsOutdated) + { + var file = await session.GetFile(RelativePath, model.Thread.OriginalCommitSha); + thread = file.InlineCommentThreads.FirstOrDefault(t => t.Comments.Any(c => c.Comment.Id == model.Id)); + } + else + { + var file = await session.GetFile(RelativePath, model.Thread.CommitSha); + thread = file.InlineCommentThreads.FirstOrDefault(t => t.Comments.Any(c => c.Comment.Id == model.Id)); + + if(thread?.LineNumber == -1) + { + log.Warning("Couldn't find line number for comment on {RelativePath} @ {CommitSha}", RelativePath, model.Thread.CommitSha); + // Fall back to opening outdated file if we can't find a line number for the comment + file = await session.GetFile(RelativePath, model.Thread.OriginalCommitSha); + thread = file.InlineCommentThreads.FirstOrDefault(t => t.Comments.Any(c => c.Comment.Id == model.Id)); + } + } } if (thread != null && thread.LineNumber != -1) @@ -61,9 +81,9 @@ async Task DoOpen() await editorService.OpenDiff(session, RelativePath, thread); } } - catch (Exception) + catch (Exception e) { - // TODO: Show error. + log.Error(e, nameof(DoOpen)); } } } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs b/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs index 718a906f1a..2f1db0020f 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs +++ b/src/GitHub.App/ViewModels/GitHubPane/PullRequestUserReviewsViewModel.cs @@ -144,7 +144,6 @@ async Task Load(PullRequestDetailModel pullRequest) try { - await Task.Delay(0); PullRequestTitle = pullRequest.Title; var reviews = new List(); diff --git a/src/GitHub.App/ViewModels/InlineAnnotationViewModel.cs b/src/GitHub.App/ViewModels/InlineAnnotationViewModel.cs new file mode 100644 index 0000000000..c395a9abef --- /dev/null +++ b/src/GitHub.App/ViewModels/InlineAnnotationViewModel.cs @@ -0,0 +1,21 @@ +using GitHub.Models; +using GitHub.ViewModels; + +namespace GitHub.ViewModels +{ + /// + public class InlineAnnotationViewModel: IInlineAnnotationViewModel + { + /// + public InlineAnnotationModel Model { get; } + + /// + /// Initializes a . + /// + /// The inline annotation model. + public InlineAnnotationViewModel(InlineAnnotationModel model) + { + Model = model; + } + } +} \ No newline at end of file diff --git a/src/GitHub.App/ViewModels/IssueishViewModel.cs b/src/GitHub.App/ViewModels/IssueishViewModel.cs new file mode 100644 index 0000000000..ba79091eda --- /dev/null +++ b/src/GitHub.App/ViewModels/IssueishViewModel.cs @@ -0,0 +1,85 @@ +using System; +using System.ComponentModel.Composition; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Logging; +using GitHub.Models; +using ReactiveUI; +using Serilog; + +namespace GitHub.ViewModels +{ + /// + /// Base class for issue and pull request view models. + /// + public class IssueishViewModel : ViewModelBase, IIssueishViewModel + { + static readonly ILogger log = LogManager.ForContext(); + + IActorViewModel author; + string body; + string title; + Uri webUrl; + + /// + /// Initializes a new instance of the class. + /// + [ImportingConstructor] + public IssueishViewModel() + { + } + + /// + public RemoteRepositoryModel Repository { get; private set; } + + /// + public string Id { get; private set; } + + /// + public int Number { get; private set; } + + /// + public IActorViewModel Author + { + get => author; + private set => this.RaiseAndSetIfChanged(ref author, value); + } + + /// + public string Body + { + get => body; + protected set => this.RaiseAndSetIfChanged(ref body, value); + } + + /// + public string Title + { + get => title; + protected set => this.RaiseAndSetIfChanged(ref title, value); + } + + /// + public Uri WebUrl + { + get { return webUrl; } + protected set { this.RaiseAndSetIfChanged(ref webUrl, value); } + } + + /// + public ReactiveCommand OpenOnGitHub { get; protected set; } + + protected Task InitializeAsync( + RemoteRepositoryModel repository, + IssueishDetailModel model) + { + Repository = repository; + Id = model.Id; + Author = new ActorViewModel(model.Author); + Body = model.Body; + Number = model.Number; + Title = model.Title; + return Task.CompletedTask; + } + } +} diff --git a/src/GitHub.App/ViewModels/PullRequestReviewCommentThreadViewModel.cs b/src/GitHub.App/ViewModels/PullRequestReviewCommentThreadViewModel.cs index 43b0d299ed..5d6b1a12c6 100644 --- a/src/GitHub.App/ViewModels/PullRequestReviewCommentThreadViewModel.cs +++ b/src/GitHub.App/ViewModels/PullRequestReviewCommentThreadViewModel.cs @@ -1,6 +1,8 @@ using System; +using System.Collections.Generic; using System.ComponentModel.Composition; using System.Globalization; +using System.IO; using System.Linq; using System.Reactive.Linq; using System.Threading.Tasks; @@ -22,6 +24,7 @@ namespace GitHub.ViewModels [PartCreationPolicy(CreationPolicy.NonShared)] public class PullRequestReviewCommentThreadViewModel : CommentThreadViewModel, IPullRequestReviewCommentThreadViewModel { + readonly ReactiveList comments = new ReactiveList(); readonly IViewViewModelFactory factory; readonly ObservableAsPropertyHelper needsPush; IPullRequestSessionFile file; @@ -49,6 +52,9 @@ public PullRequestReviewCommentThreadViewModel( .ToProperty(this, x => x.NeedsPush); } + /// + public IReactiveList Comments => comments; + /// public IPullRequestSession Session { get; private set; } @@ -65,6 +71,9 @@ public IPullRequestSessionFile File /// public DiffSide Side { get; private set; } + /// + public bool IsResolved { get; private set; } + public bool IsNewThread { get => isNewThread; @@ -74,6 +83,9 @@ public bool IsNewThread /// public bool NeedsPush => needsPush.Value; + /// + IReadOnlyReactiveList IPullRequestReviewCommentThreadViewModel.Comments => comments; + /// public async Task InitializeAsync( IPullRequestSession session, @@ -89,6 +101,7 @@ public async Task InitializeAsync( File = file; LineNumber = thread.LineNumber; Side = thread.DiffLineType == DiffChangeType.Delete ? DiffSide.Left : DiffSide.Right; + IsResolved = thread.IsResolved; foreach (var comment in thread.Comments) { @@ -121,7 +134,8 @@ await vm.InitializeAsPlaceholderAsync( vm.Body = draft.Body; } - AddPlaceholder(vm); + InitializePlaceholder(vm); + comments.Add(vm); } } @@ -154,7 +168,8 @@ public async Task InitializeNewAsync( vm.Body = draft.Body; } - AddPlaceholder(vm); + InitializePlaceholder(vm); + comments.Add(vm); } public override async Task PostComment(ICommentViewModel comment) @@ -183,7 +198,7 @@ public override async Task PostComment(ICommentViewModel comment) await Session.PostReviewComment( comment.Body, File.CommitSha, - File.RelativePath.Replace("\\", "/"), + Paths.ToGitPath(File.RelativePath), File.Diff, diffPosition.DiffLineNumber).ConfigureAwait(false); } @@ -220,8 +235,8 @@ public static (string key, string secondaryKey) GetDraftKeys( string relativePath, int lineNumber) { - relativePath = relativePath.Replace("\\", "/"); - var key = Invariant($"pr-review-comment|{cloneUri}|{pullRequestNumber}|{relativePath}"); + var gitPath = Paths.ToGitPath(relativePath); + var key = Invariant($"pr-review-comment|{cloneUri}|{pullRequestNumber}|{gitPath}"); return (key, lineNumber.ToString(CultureInfo.InvariantCulture)); } diff --git a/src/GitHub.App/ViewModels/PullRequestReviewCommentViewModel.cs b/src/GitHub.App/ViewModels/PullRequestReviewCommentViewModel.cs index dc92301ed0..4a2c1a8e97 100644 --- a/src/GitHub.App/ViewModels/PullRequestReviewCommentViewModel.cs +++ b/src/GitHub.App/ViewModels/PullRequestReviewCommentViewModel.cs @@ -19,31 +19,25 @@ namespace GitHub.ViewModels public class PullRequestReviewCommentViewModel : CommentViewModel, IPullRequestReviewCommentViewModel { readonly ObservableAsPropertyHelper canStartReview; - readonly ObservableAsPropertyHelper commitCaption; IPullRequestSession session; bool isPending; /// /// Initializes a new instance of the class. /// - /// The comment service + /// The comment service. + /// The auto complete advisor. [ImportingConstructor] - public PullRequestReviewCommentViewModel(ICommentService commentService) - : base(commentService) + public PullRequestReviewCommentViewModel(ICommentService commentService, + IAutoCompleteAdvisor autoCompleteAdvisor) + : base(commentService, autoCompleteAdvisor) { - var pendingAndIsNew = this.WhenAnyValue( + canStartReview = this.WhenAnyValue( x => x.IsPending, x => x.Id, - (isPending, id) => (isPending, isNewComment: id == null)); - - canStartReview = pendingAndIsNew - .Select(arg => !arg.isPending && arg.isNewComment) + (isPending, id) => !isPending && id == null) .ToProperty(this, x => x.CanStartReview); - commitCaption = pendingAndIsNew - .Select(arg => !arg.isNewComment ? Resources.UpdateComment : arg.isPending ? Resources.AddReviewComment : Resources.AddSingleComment) - .ToProperty(this, x => x.CommitCaption); - StartReview = ReactiveCommand.CreateFromTask(DoStartReview, CommitEdit.CanExecute); AddErrorHandler(StartReview); } @@ -84,9 +78,6 @@ await InitializeAsync( /// public bool CanStartReview => canStartReview.Value; - /// - public string CommitCaption => commitCaption.Value; - /// public bool IsPending { @@ -97,6 +88,16 @@ public bool IsPending /// public ReactiveCommand StartReview { get; } + protected override IObservable GetCommitCaptionObservable() + { + return this.WhenAnyValue( + x => x.IsPending, + x => x.Id, + (pending, id) => id != null ? + Resources.UpdateComment : + pending ? Resources.AddReviewComment : Resources.AddSingleComment); + } + async Task DoStartReview() { IsSubmitting = true; diff --git a/src/GitHub.App/ViewModels/PullRequestViewModelBase.cs b/src/GitHub.App/ViewModels/PullRequestViewModelBase.cs new file mode 100644 index 0000000000..d831effced --- /dev/null +++ b/src/GitHub.App/ViewModels/PullRequestViewModelBase.cs @@ -0,0 +1,78 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using GitHub.Extensions; +using GitHub.Logging; +using GitHub.Models; +using ReactiveUI; +using Serilog; + +namespace GitHub.ViewModels +{ + /// + /// Base class for pull request view models. + /// + public class PullRequestViewModelBase : IssueishViewModel, IPullRequestViewModelBase + { + static readonly ILogger log = LogManager.ForContext(); + PullRequestState state; + string sourceBranchDisplayName; + string targetBranchDisplayName; + + /// + /// Initializes a new instance of the class. + /// + [ImportingConstructor] + public PullRequestViewModelBase() + { + } + + /// + public LocalRepositoryModel LocalRepository { get; private set; } + + public PullRequestState State + { + get => state; + protected set => this.RaiseAndSetIfChanged(ref state, value); + } + + public string SourceBranchDisplayName + { + get => sourceBranchDisplayName; + private set => this.RaiseAndSetIfChanged(ref sourceBranchDisplayName, value); + } + + public string TargetBranchDisplayName + { + get => targetBranchDisplayName; + private set => this.RaiseAndSetIfChanged(ref targetBranchDisplayName, value); + } + + protected virtual async Task InitializeAsync( + RemoteRepositoryModel repository, + LocalRepositoryModel localRepository, + PullRequestDetailModel model) + { + await base.InitializeAsync(repository, model).ConfigureAwait(true); + + var fork = model.BaseRepositoryOwner != model.HeadRepositoryOwner; + LocalRepository = localRepository; + State = model.State; + SourceBranchDisplayName = GetBranchDisplayName(fork, model.HeadRepositoryOwner, model.HeadRefName); + TargetBranchDisplayName = GetBranchDisplayName(fork, model.BaseRepositoryOwner, model.BaseRefName); + WebUrl = localRepository.CloneUrl.ToRepositoryUrl().Append("pull/" + Number); + } + + static string GetBranchDisplayName(bool isFromFork, string owner, string label) + { + if (owner != null) + { + return isFromFork ? owner + ':' + label : label; + } + else + { + return Resources.InvalidBranchName; + } + } + } +} diff --git a/src/GitHub.App/ViewModels/RepositoryFormViewModel.cs b/src/GitHub.App/ViewModels/RepositoryFormViewModel.cs index 6aa13f02b1..bd69c9b141 100644 --- a/src/GitHub.App/ViewModels/RepositoryFormViewModel.cs +++ b/src/GitHub.App/ViewModels/RepositoryFormViewModel.cs @@ -17,17 +17,6 @@ public abstract class RepositoryFormViewModel : ViewModelBase protected RepositoryFormViewModel() { - CanKeepPrivateObservable = this.WhenAny( - x => x.SelectedAccount.IsEnterprise, - x => x.SelectedAccount.IsOnFreePlan, - x => x.SelectedAccount.HasMaximumPrivateRepositories, - (isEnterprise, isOnFreePlan, hasMaxPrivateRepos) => - isEnterprise.Value || (!isOnFreePlan.Value && !hasMaxPrivateRepos.Value)); - - CanKeepPrivateObservable - .Where(x => !x) - .Subscribe(x => KeepPrivate = false); - safeRepositoryName = this.WhenAny(x => x.RepositoryName, x => x.Value) .Select(x => x != null ? GetSafeRepositoryName(x) : null) .ToProperty(this, x => x.SafeRepositoryName); @@ -85,14 +74,6 @@ public IAccount SelectedAccount set { this.RaiseAndSetIfChanged(ref selectedAccount, value); } } - public bool ShowUpgradePlanWarning { get; private set; } - - public bool ShowUpgradeToMicroPlanWarning { get; private set; } - - public ICommand UpgradeAccountPlan { get; private set; } - - protected IObservable CanKeepPrivateObservable { get; private set; } - // These are the characters which are permitted when creating a repository name on GitHub The Website static readonly Regex invalidRepositoryCharsRegex = new Regex(@"[^0-9A-Za-z_\.\-]", RegexOptions.ECMAScript); diff --git a/src/GitHub.App/ViewModels/SpinnerViewModel.cs b/src/GitHub.App/ViewModels/SpinnerViewModel.cs new file mode 100644 index 0000000000..3395ca8789 --- /dev/null +++ b/src/GitHub.App/ViewModels/SpinnerViewModel.cs @@ -0,0 +1,14 @@ +using System; +using System.ComponentModel.Composition; + +namespace GitHub.ViewModels +{ + /// + /// View model which displays a spinner. + /// + [Export(typeof(ISpinnerViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class SpinnerViewModel : ViewModelBase, ISpinnerViewModel + { + } +} diff --git a/src/GitHub.App/ViewModels/TeamExplorer/RepositoryPublishViewModel.cs b/src/GitHub.App/ViewModels/TeamExplorer/RepositoryPublishViewModel.cs index 276cbeec1f..3ad6d48e30 100644 --- a/src/GitHub.App/ViewModels/TeamExplorer/RepositoryPublishViewModel.cs +++ b/src/GitHub.App/ViewModels/TeamExplorer/RepositoryPublishViewModel.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Reactive; using System.Reactive.Linq; -using GitHub.App; +using System.Threading.Tasks; using GitHub.Extensions; using GitHub.Extensions.Reactive; using GitHub.Factories; @@ -29,10 +29,9 @@ public class RepositoryPublishViewModel : RepositoryFormViewModel, IRepositoryPu readonly IRepositoryPublishService repositoryPublishService; readonly INotificationService notificationService; readonly IModelServiceFactory modelServiceFactory; + readonly IDialogService dialogService; readonly ObservableAsPropertyHelper> accounts; readonly ObservableAsPropertyHelper isHostComboBoxVisible; - readonly ObservableAsPropertyHelper canKeepPrivate; - readonly ObservableAsPropertyHelper title; readonly IUsageTracker usageTracker; [ImportingConstructor] @@ -41,6 +40,7 @@ public RepositoryPublishViewModel( INotificationService notificationService, IConnectionManager connectionManager, IModelServiceFactory modelServiceFactory, + IDialogService dialogService, IUsageTracker usageTracker) { Guard.ArgumentNotNull(repositoryPublishService, nameof(repositoryPublishService)); @@ -48,18 +48,12 @@ public RepositoryPublishViewModel( Guard.ArgumentNotNull(connectionManager, nameof(connectionManager)); Guard.ArgumentNotNull(usageTracker, nameof(usageTracker)); Guard.ArgumentNotNull(modelServiceFactory, nameof(modelServiceFactory)); + Guard.ArgumentNotNull(dialogService, nameof(dialogService)); this.notificationService = notificationService; this.usageTracker = usageTracker; this.modelServiceFactory = modelServiceFactory; - - title = this.WhenAny( - x => x.SelectedConnection, - x => x.Value != null ? - string.Format(CultureInfo.CurrentCulture, Resources.PublishToTitle, x.Value.HostAddress.Title) : - Resources.PublishTitle - ) - .ToProperty(this, x => x.Title); + this.dialogService = dialogService; Connections = connectionManager.Connections; this.repositoryPublishService = repositoryPublishService; @@ -91,13 +85,10 @@ public RepositoryPublishViewModel( InitializeValidation(); PublishRepository = InitializePublishRepositoryCommand(); - - canKeepPrivate = CanKeepPrivateObservable.CombineLatest(PublishRepository.IsExecuting, - (canKeep, publishing) => canKeep && !publishing) - .ToProperty(this, x => x.CanKeepPrivate); - PublishRepository.IsExecuting.Subscribe(x => IsBusy = x); + LoginAsDifferentUser = ReactiveCommand.CreateFromTask(LoginAsDifferentUserAsync); + var defaultRepositoryName = repositoryPublishService.LocalRepositoryName; if (!string.IsNullOrEmpty(defaultRepositoryName)) RepositoryName = defaultRepositoryName; @@ -115,10 +106,10 @@ public RepositoryPublishViewModel( }); } - public string Title { get { return title.Value; } } - public bool CanKeepPrivate { get { return canKeepPrivate.Value; } } - public ReactiveCommand PublishRepository { get; private set; } + + public ReactiveCommand LoginAsDifferentUser { get; private set; } + public IReadOnlyObservableCollection Connections { get; private set; } bool isBusy; @@ -145,6 +136,14 @@ public bool IsHostComboBoxVisible get { return isHostComboBoxVisible.Value; } } + async Task LoginAsDifferentUserAsync() + { + if (await dialogService.ShowLoginDialog() is IConnection connection) + { + SelectedConnection = connection; + } + } + ReactiveCommand InitializePublishRepositoryCommand() { var canCreate = this.WhenAny(x => x.RepositoryNameValidator.ValidationResult.IsValid, x => x.Value); diff --git a/src/GitHub.App/ViewModels/UserFilterViewModel.cs b/src/GitHub.App/ViewModels/UserFilterViewModel.cs index 2409717159..3cf2020f89 100644 --- a/src/GitHub.App/ViewModels/UserFilterViewModel.cs +++ b/src/GitHub.App/ViewModels/UserFilterViewModel.cs @@ -146,7 +146,10 @@ public int Compare(object x, object y) { if (x == owner.ersatzUser) return -1; if (y == owner.ersatzUser) return 1; - return ((IActorViewModel)x).Login.CompareTo(((IActorViewModel)y).Login); + return string.Compare( + ((IActorViewModel)x).Login, + ((IActorViewModel)y).Login, + StringComparison.Ordinal); } } } diff --git a/src/GitHub.App/sqlite-net/SQLite.cs b/src/GitHub.App/sqlite-net/SQLite.cs index 6861b8c0eb..96a3b36728 100644 --- a/src/GitHub.App/sqlite-net/SQLite.cs +++ b/src/GitHub.App/sqlite-net/SQLite.cs @@ -54,6 +54,23 @@ #endif #pragma warning disable 1591 // XML Doc Comments +#pragma warning disable CA1018 // Mark attributes with AttributeUsageAttribute +#pragma warning disable CA1032 // Implement standard exception constructors +#pragma warning disable CA1034 // Nested types should not be visible +#pragma warning disable CA1051 // Do not declare visible instance fields +#pragma warning disable CA1052 // Static holder types should be Static or NotInheritable +#pragma warning disable CA1060 // Move pinvokes to native methods class +#pragma warning disable CA1304 // Specify CultureInfo +#pragma warning disable CA1305 // Specify IFormatProvider +#pragma warning disable CA1401 // P/Invokes should not be visible +#pragma warning disable CA1710 // Identifiers should have correct suffix +#pragma warning disable CA1720 // Identifier contains type name +#pragma warning disable CA1806 // Do not ignore method results +#pragma warning disable CA1819 // Properties should not return arrays +#pragma warning disable CA2101 // Specify marshaling for P/Invoke string arguments +#pragma warning disable CA2200 // Rethrow to preserve stack details. +#pragma warning disable CA2208 // Instantiate argument exceptions correctly +#pragma warning disable CA2237 // Mark ISerializable types with serializable namespace SQLite { diff --git a/src/GitHub.App/sqlite-net/SQLiteAsync.cs b/src/GitHub.App/sqlite-net/SQLiteAsync.cs index 77d4fb0388..e41fa43e53 100644 --- a/src/GitHub.App/sqlite-net/SQLiteAsync.cs +++ b/src/GitHub.App/sqlite-net/SQLiteAsync.cs @@ -28,6 +28,10 @@ using System.Threading; using System.Threading.Tasks; +#pragma warning disable CA1001 // Types that own disposable fields should be disposable +#pragma warning disable CA1200 // Avoid using cref tags with a prefix +#pragma warning disable CA1720 // Identifier contains type name + namespace SQLite { /// diff --git a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj index d9ded20ab2..5a1579f915 100644 --- a/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj +++ b/src/GitHub.Exports.Reactive/GitHub.Exports.Reactive.csproj @@ -27,6 +27,5 @@ - diff --git a/src/GitHub.Exports.Reactive/GlobalSuppressions.cs b/src/GitHub.Exports.Reactive/GlobalSuppressions.cs deleted file mode 100644 index 2538500fd8..0000000000 Binary files a/src/GitHub.Exports.Reactive/GlobalSuppressions.cs and /dev/null differ diff --git a/src/GitHub.Exports.Reactive/Helpers/ExceptionHelper.cs b/src/GitHub.Exports.Reactive/Helpers/ExceptionHelper.cs index 753471d89e..deb1bcefa4 100644 --- a/src/GitHub.Exports.Reactive/Helpers/ExceptionHelper.cs +++ b/src/GitHub.Exports.Reactive/Helpers/ExceptionHelper.cs @@ -3,6 +3,8 @@ using System.Globalization; using System.Reactive.Linq; +#pragma warning disable CA1720 // Identifier contains type name + namespace GitHub.Helpers { public static class ExceptionHelper diff --git a/src/GitHub.Exports.Reactive/Models/AutoCompleteResult.cs b/src/GitHub.Exports.Reactive/Models/AutoCompleteResult.cs new file mode 100644 index 0000000000..c9461025f9 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Models/AutoCompleteResult.cs @@ -0,0 +1,18 @@ +using System.Collections.Generic; + +namespace GitHub.Models +{ + public class AutoCompleteResult + { + public static AutoCompleteResult Empty = new AutoCompleteResult(0, new AutoCompleteSuggestion[] {}); + + public AutoCompleteResult(int offset, IReadOnlyList suggestions) + { + Offset = offset; + Suggestions = suggestions; + } + + public int Offset { get; private set; } + public IReadOnlyList Suggestions { get; private set; } + } +} diff --git a/src/GitHub.Exports.Reactive/Models/AutoCompleteSuggestion.cs b/src/GitHub.Exports.Reactive/Models/AutoCompleteSuggestion.cs new file mode 100644 index 0000000000..e00e67274e --- /dev/null +++ b/src/GitHub.Exports.Reactive/Models/AutoCompleteSuggestion.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reactive.Linq; +using System.Windows.Media.Imaging; +using GitHub.Extensions; +using GitHub.Helpers; +using ReactiveUI; + +namespace GitHub.Models +{ + public class AutoCompleteSuggestion + { + readonly string prefix; + readonly string suffix; + readonly string[] descriptionWords; + + public AutoCompleteSuggestion(string name, string description, string prefix) + : this(name, description, Observable.Return(null), prefix) + { + } + + public AutoCompleteSuggestion(string name, string description, IObservable image, string prefix) + : this(name, description, image, prefix, null) + { + } + + public AutoCompleteSuggestion(string name, IObservable image, string prefix, string suffix) + : this(name, null, image, prefix, suffix) + { + } + + public AutoCompleteSuggestion(string name, string description, IObservable image, string prefix, string suffix) + { + Guard.ArgumentNotEmptyString(name, "name"); + Guard.ArgumentNotEmptyString(prefix, "prefix"); // Suggestions have to have a triggering prefix. + Guard.ArgumentNotNull(image, "image"); + + Name = name; + Description = description; + if (image != null) + { + image = image.ObserveOn(RxApp.MainThreadScheduler); + } + Image = image; + + this.prefix = prefix; + this.suffix = suffix; + + // This is pretty naive, but since the Description is currently limited to a user's FullName, + // This is fine. When we add #issue completion, we may need to fancy this up a bit. + descriptionWords = (description ?? String.Empty) + .Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries); + } + + /// + /// The name to display in the autocomplete list box. This should not have the "@" or ":" characters around it. + /// + public string Name { get; private set; } + + public string Description { get; private set; } + + public IObservable Image { get; private set; } + + protected IReadOnlyCollection DescriptionWords { get { return descriptionWords; } } + + // What gets autocompleted. + public override string ToString() + { + return prefix + Name + suffix; + } + + /// + /// Used to determine if the suggestion matches the text and if so, how it should be sorted. The larger the + /// rank, the higher it sorts. + /// + /// + /// For mentions we sort suggestions in the following order: + /// + /// 1. Login starts with text + /// 2. Component of Name starts with text (split name by spaces, then match each word) + /// + /// Non matches return -1. The secondary sort is by Login ascending. + /// + /// The suggestion text to match + /// -1 for non-match and the sort order described in the remarks for matches + public virtual int GetSortRank(string text) + { + Guard.ArgumentNotNull(text, "text"); + + return Name.StartsWith(text, StringComparison.OrdinalIgnoreCase) + ? 1 + : descriptionWords.Any(word => word.StartsWith(text, StringComparison.OrdinalIgnoreCase)) + ? 0 + : -1; + } + } +} diff --git a/src/GitHub.Exports.Reactive/Models/IInlineCommentThreadModel.cs b/src/GitHub.Exports.Reactive/Models/IInlineCommentThreadModel.cs index ed5cd93908..802971c167 100644 --- a/src/GitHub.Exports.Reactive/Models/IInlineCommentThreadModel.cs +++ b/src/GitHub.Exports.Reactive/Models/IInlineCommentThreadModel.cs @@ -49,5 +49,10 @@ public interface IInlineCommentThreadModel /// Gets the relative path to the file that the thread is on. /// string RelativePath { get; } + + /// + /// Gets a value indicating whether comment thread has been marked as resolved by a user. + /// + bool IsResolved { get; } } } diff --git a/src/GitHub.Exports.Reactive/Models/IPullRequestSessionFile.cs b/src/GitHub.Exports.Reactive/Models/IPullRequestSessionFile.cs index e13eb31a6c..0cd2a87cf0 100644 --- a/src/GitHub.Exports.Reactive/Models/IPullRequestSessionFile.cs +++ b/src/GitHub.Exports.Reactive/Models/IPullRequestSessionFile.cs @@ -54,6 +54,11 @@ public interface IPullRequestSessionFile : INotifyPropertyChanged /// IReadOnlyList InlineCommentThreads { get; } + /// + /// Gets the inline annotations for the file. + /// + IReadOnlyList InlineAnnotations { get; } + /// /// Gets an observable that is raised with a collection of 0-based line numbers when the /// review comments on the file are changed. diff --git a/src/GitHub.Exports.Reactive/Models/InlineAnnotationModel.cs b/src/GitHub.Exports.Reactive/Models/InlineAnnotationModel.cs new file mode 100644 index 0000000000..cee1391194 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Models/InlineAnnotationModel.cs @@ -0,0 +1,82 @@ +using GitHub.Extensions; + +namespace GitHub.Models +{ + /// + /// Represents an inline annotation on an . + /// + public class InlineAnnotationModel + { + readonly CheckSuiteModel checkSuite; + readonly CheckRunModel checkRun; + readonly CheckRunAnnotationModel annotation; + + /// + /// Initializes the . + /// + /// The check suite model. + /// The check run model. + /// The annotation model. + public InlineAnnotationModel(CheckSuiteModel checkSuite, CheckRunModel checkRun, + CheckRunAnnotationModel annotation) + { + Guard.ArgumentNotNull(checkRun, nameof(checkRun)); + Guard.ArgumentNotNull(annotation, nameof(annotation)); + Guard.ArgumentNotNull(annotation.AnnotationLevel, nameof(annotation.AnnotationLevel)); + + this.checkSuite = checkSuite; + this.checkRun = checkRun; + this.annotation = annotation; + } + + /// + /// Gets the annotation path. + /// + public string Path => annotation.Path; + + /// + /// Gets the 1-based start line of the annotation. + /// + public int StartLine => annotation.StartLine; + + /// + /// Gets the 1-based end line of the annotation. + /// + public int EndLine => annotation.EndLine; + + /// + /// Gets the annotation level. + /// + public CheckAnnotationLevel AnnotationLevel => annotation.AnnotationLevel; + + /// + /// Gets the name of the check suite. + /// + public string CheckSuiteName => checkSuite.ApplicationName; + + /// + /// Gets the name of the check run. + /// + public string CheckRunName => checkRun.Name; + + /// + /// Gets the annotation title. + /// + public string Title => annotation.Title; + + /// + /// Gets the annotation message. + /// + public string Message => annotation.Message; + + /// + /// Gets the sha the check run was created on. + /// + public string HeadSha => checkSuite.HeadSha; + + /// + /// Gets the a descriptor for the line(s) reported. + /// + public string LineDescription => $"{StartLine}:{EndLine}"; + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/Models/RemoteRepositoryModel.cs b/src/GitHub.Exports.Reactive/Models/RemoteRepositoryModel.cs index 838dda5b8e..dd33e6f8a4 100644 --- a/src/GitHub.Exports.Reactive/Models/RemoteRepositoryModel.cs +++ b/src/GitHub.Exports.Reactive/Models/RemoteRepositoryModel.cs @@ -28,7 +28,6 @@ public RemoteRepositoryModel(long id, string name, UriString cloneUrl, bool isPr : base(name, cloneUrl) { Guard.ArgumentNotEmptyString(name, nameof(name)); - Guard.ArgumentNotNull(ownerAccount, nameof(ownerAccount)); Id = id; OwnerAccount = ownerAccount; diff --git a/src/GitHub.Exports.Reactive/Services/IAutoCompleteAdvisor.cs b/src/GitHub.Exports.Reactive/Services/IAutoCompleteAdvisor.cs new file mode 100644 index 0000000000..b071be1bff --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IAutoCompleteAdvisor.cs @@ -0,0 +1,10 @@ +using System; +using GitHub.Models; + +namespace GitHub.Services +{ + public interface IAutoCompleteAdvisor + { + IObservable GetAutoCompletionSuggestions(string text, int caretPosition); + } +} diff --git a/src/GitHub.Exports.Reactive/Services/IGitClient.cs b/src/GitHub.Exports.Reactive/Services/IGitClient.cs index 81fbbafebe..192612f9fa 100644 --- a/src/GitHub.Exports.Reactive/Services/IGitClient.cs +++ b/src/GitHub.Exports.Reactive/Services/IGitClient.cs @@ -25,20 +25,12 @@ public interface IGitClient /// Task Push(IRepository repository, string branchName, string remoteName); - /// - /// Fetches the remote. - /// - /// The repository to pull - /// The name of the remote - /// - Task Fetch(IRepository repository, string remoteName); - /// /// Fetches from the remote, using custom refspecs. /// /// The repository to pull /// The name of the remote - /// The custom refspecs + /// The custom refspecs or none to use the default /// Task Fetch(IRepository repository, string remoteName, params string[] refspecs); @@ -64,6 +56,14 @@ public interface IGitClient /// Task Checkout(IRepository repository, string branchName); + /// + /// Checks if a commit exists a the repository. + /// + /// The repository. + /// The SHA of the commit. + /// + Task CommitExists(IRepository repository, string sha); + /// /// Creates a new branch. /// @@ -72,44 +72,6 @@ public interface IGitClient /// Task CreateBranch(IRepository repository, string branchName); - /// - /// Compares two commits. - /// - /// The repository - /// The SHA of the first commit. - /// The SHA of the second commit. - /// Whether to detect renames - /// - /// A object or null if one of the commits could not be found in the repository, - /// (e.g. it is from a fork). - /// - Task Compare(IRepository repository, string sha1, string sha2, bool detectRenames = false); - - /// - /// Compares a file in two commits. - /// - /// The repository - /// The SHA of the first commit. - /// The SHA of the second commit. - /// The relative path to the file. - /// - /// A object or null if one of the commits could not be found in the repository. - /// - Task Compare(IRepository repository, string sha1, string sha2, string path); - - /// - /// Compares a file in a commit to a string. - /// - /// The repository - /// The SHA of the first commit. - /// The SHA of the second commit. - /// The relative path to the file. - /// The contents to compare with the file. - /// - /// A object or null if the commit could not be found in the repository. - /// - Task CompareWith(IRepository repository, string sha1, string sha2, string path, byte[] contents); - /// /// Gets the value of a configuration key. /// diff --git a/src/GitHub.Exports.Reactive/Services/IInlineCommentPeekService.cs b/src/GitHub.Exports.Reactive/Services/IInlineCommentPeekService.cs index 219f657241..357ba17b84 100644 --- a/src/GitHub.Exports.Reactive/Services/IInlineCommentPeekService.cs +++ b/src/GitHub.Exports.Reactive/Services/IInlineCommentPeekService.cs @@ -12,12 +12,12 @@ namespace GitHub.Services public interface IInlineCommentPeekService { /// - /// Gets the line number for a peek session tracking point. + /// Gets the 0-based line number for a peek session tracking point. /// /// The peek session. /// The peek session tracking point /// - /// A tuple containing the line number and whether the line number represents a line in the + /// A tuple containing the 0-based line number and whether the line number represents a line in the /// left hand side of a diff view. /// Tuple GetLineNumber(IPeekSession session, ITrackingPoint point); diff --git a/src/GitHub.Exports.Reactive/Services/IIssueishService.cs b/src/GitHub.Exports.Reactive/Services/IIssueishService.cs new file mode 100644 index 0000000000..e4a3ba55e6 --- /dev/null +++ b/src/GitHub.Exports.Reactive/Services/IIssueishService.cs @@ -0,0 +1,71 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; +using GitHub.Primitives; + +namespace GitHub.Services +{ + /// + /// Services for issues and pull requests. + /// + public interface IIssueishService + { + /// + /// Closes an issue or pull request. + /// + /// The address of the server. + /// The repository owner. + /// The repository name. + /// The issue or pull request number. + Task CloseIssueish(HostAddress address, string owner, string repository, int number); + + /// + /// Reopens an issue or pull request. + /// + /// The address of the server. + /// The repository owner. + /// The repository name. + /// The issue or pull request number. + Task ReopenIssueish(HostAddress address, string owner, string repository, int number); + + /// + /// Posts an issue or pull request comment. + /// + /// The address of the server. + /// The GraphQL ID of the issue or pull request. + /// The comment body. + /// The model for the comment that was added. + Task PostComment( + HostAddress address, + string issueishId, + string body); + + /// + /// Deletes an issue or pull request comment. + /// + /// The address of the server. + /// The repository owner. + /// The repository name. + /// The database ID of the comment. + Task DeleteComment( + HostAddress address, + string owner, + string repository, + int commentId); + + /// + /// Edits an issue or pull request comment. + /// + /// The address of the server. + /// The repository owner. + /// The repository name. + /// The database ID of the comment. + /// The new comment body. + Task EditComment( + HostAddress address, + string owner, + string repository, + int commentId, + string body); + } +} diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs index ec5e274422..53855dc416 100644 --- a/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestEditorService.cs @@ -37,8 +37,8 @@ public interface IPullRequestEditorService Task OpenDiff(IPullRequestSession session, string relativePath, string headSha = null, bool scrollToFirstDiff = true); /// - /// Opens an diff viewer for a file in a pull request with the specified inline comment - /// thread open. + /// Opens an diff viewer for a file in a pull request with the specified inline review + /// comment thread open. /// /// The pull request session. /// The path to the file, relative to the repository. @@ -46,6 +46,19 @@ public interface IPullRequestEditorService /// The opened diff viewer. Task OpenDiff(IPullRequestSession session, string relativePath, IInlineCommentThreadModel thread); + /// + /// Opens an diff viewer for a file in a pull request with the specified inline review line open. + /// + /// The pull request session. + /// The path to the file, relative to the repository. + /// + /// The commit SHA of the right hand side of the diff. Pass null to compare with the + /// working directory, or "HEAD" to compare with the HEAD commit of the pull request. + /// + /// The 0-based line number to execute NextInlineCommentCommand from + /// The opened diff viewer. + Task OpenDiff(IPullRequestSession session, string relativePath, string headSha, int nextInlineTagFromLine); + /// /// Find the active text view. /// diff --git a/src/GitHub.Exports.Reactive/Services/IPullRequestService.cs b/src/GitHub.Exports.Reactive/Services/IPullRequestService.cs index 29e8e1d5f4..e472ffd47a 100644 --- a/src/GitHub.Exports.Reactive/Services/IPullRequestService.cs +++ b/src/GitHub.Exports.Reactive/Services/IPullRequestService.cs @@ -9,7 +9,7 @@ namespace GitHub.Services { - public interface IPullRequestService + public interface IPullRequestService : IIssueishService { /// /// Reads a page of pull request items. @@ -19,13 +19,22 @@ public interface IPullRequestService /// The repository name. /// The end cursor of the previous page, or null for the first page. /// The pull request states to filter by + /// Whether the data should be refreshed instead of read from the cache. /// A page of pull request item models. Task> ReadPullRequests( HostAddress address, string owner, string name, string after, - PullRequestStateEnum[] states); + PullRequestState[] states); + + /// + /// Clears the cache for . + /// + /// The host address. + /// The repository owner. + /// The repository name. + Task ClearPullRequestsCache(HostAddress address, string owner, string name); /// /// Reads a page of users that can be assigned to pull requests. @@ -69,6 +78,15 @@ IObservable CreatePullRequest(IModelService modelService, /// IObservable Checkout(LocalRepositoryModel repository, PullRequestDetailModel pullRequest, string localBranchName); + /// + /// Checks if a commit is available and if not tries to fetch the commit. + /// + /// The local repository. + /// The remote repository. + /// The SHA of the commit. + /// True if the commit was found, otherwise false. + Task FetchCommit(LocalRepositoryModel localRepository, RepositoryModel remoteRepository, string sha); + /// /// Carries out a pull on the current branch. /// diff --git a/src/GitHub.Exports.Reactive/Services/IRepositoryCloneService.cs b/src/GitHub.Exports.Reactive/Services/IRepositoryCloneService.cs index a4810bf742..791aa25734 100644 --- a/src/GitHub.Exports.Reactive/Services/IRepositoryCloneService.cs +++ b/src/GitHub.Exports.Reactive/Services/IRepositoryCloneService.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using GitHub.Models; using GitHub.Primitives; @@ -27,11 +26,13 @@ public interface IRepositoryCloneService /// System.IProgress<Microsoft.VisualStudio.Shell.ServiceProgressData>, but /// as that type is only available in VS2017+ it is typed as here. /// + /// A cancellation token. /// Task CloneRepository( string cloneUrl, string repositoryPath, - object progress = null); + object progress = null, + CancellationToken? cancellationToken = null); /// /// Clones the specified repository into the specified directory or opens it if the directory already exists. @@ -45,7 +46,8 @@ Task CloneRepository( /// Task CloneOrOpenRepository( CloneDialogResult cloneDialogResult, - object progress = null); + object progress = null, + CancellationToken? cancellationToken = null); /// /// Checks whether the specified destination directory already exists. @@ -56,6 +58,15 @@ Task CloneOrOpenRepository( /// bool DestinationDirectoryExists(string path); + /// + /// Checks whether the specified destination directory is empty. + /// + /// The destination path. + /// + /// true if a directory is empty ; otherwise false. + /// + bool DestinationDirectoryEmpty(string path); + /// /// Checks whether the specified destination file already exists. /// @@ -65,6 +76,6 @@ Task CloneOrOpenRepository( /// bool DestinationFileExists(string path); - Task ReadViewerRepositories(HostAddress address); + Task ReadViewerRepositories(HostAddress address, bool refresh = false); } } diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositoryCloneViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositoryCloneViewModel.cs index 0b8eb754fc..10f3b1cb83 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositoryCloneViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositoryCloneViewModel.cs @@ -1,6 +1,7 @@ using System; using System.Reactive; using GitHub.Models; +using GitHub.Primitives; using ReactiveUI; namespace GitHub.ViewModels.Dialog.Clone @@ -21,9 +22,9 @@ public interface IRepositoryCloneViewModel : IDialogContentViewModel, IConnectio IRepositorySelectViewModel EnterpriseTab { get; } /// - /// Gets the view model for the URL tab. + /// Initial URL for the dialog. /// - IRepositoryUrlViewModel UrlTab { get; } + UriString Url { get; set; } /// /// Gets the path to clone the repository to. @@ -52,5 +53,7 @@ public interface IRepositoryCloneViewModel : IDialogContentViewModel, IConnectio /// Gets the command executed when the user clicks "Clone". /// ReactiveCommand Clone { get; } + + ReactiveCommand LoginAsDifferentUser { get; } } } diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositorySelectViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositorySelectViewModel.cs index e59837d972..b8cf181f63 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositorySelectViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositorySelectViewModel.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; using System.ComponentModel; +using System.Reactive; using System.Threading.Tasks; using GitHub.Models; +using ReactiveUI; namespace GitHub.ViewModels.Dialog.Clone { diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositoryUrlViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositoryUrlViewModel.cs deleted file mode 100644 index 57701e2948..0000000000 --- a/src/GitHub.Exports.Reactive/ViewModels/Dialog/Clone/IRepositoryUrlViewModel.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace GitHub.ViewModels.Dialog.Clone -{ - public interface IRepositoryUrlViewModel : IRepositoryCloneTabViewModel - { - string Url { get; set; } - } -} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryRecloneViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryRecloneViewModel.cs deleted file mode 100644 index 1929a198e4..0000000000 --- a/src/GitHub.Exports.Reactive/ViewModels/Dialog/IRepositoryRecloneViewModel.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using GitHub.Models; - -namespace GitHub.ViewModels.Dialog -{ - public interface IRepositoryRecloneViewModel : IDialogContentViewModel, IConnectionInitializedViewModel - { - /// - /// Gets or sets the repository to clone. - /// - RepositoryModel SelectedRepository { get; set; } - } -} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Documents/ICommitListViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Documents/ICommitListViewModel.cs new file mode 100644 index 0000000000..46ad9c13c6 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Documents/ICommitListViewModel.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.ViewModels.Documents +{ + /// + /// Displays a list of commit summaries in a pull request timeline. + /// + public interface ICommitListViewModel : IViewModel + { + /// + /// Gets the first author of the commits in the list. + /// + ICommitActorViewModel Author { get; } + + /// + /// Gets a string to display the author login or the author name. + /// + string AuthorName { get; } + + /// + /// Gets a string to display next to the author in the view. + /// + string AuthorCaption { get; } + + /// + /// Gets the commits. + /// + IReadOnlyList Commits { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/Documents/ICommitSummaryViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Documents/ICommitSummaryViewModel.cs new file mode 100644 index 0000000000..49d046b503 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Documents/ICommitSummaryViewModel.cs @@ -0,0 +1,28 @@ +namespace GitHub.ViewModels.Documents +{ + /// + /// Displays a one-line summary of a commit in a pull request timeline. + /// + public interface ICommitSummaryViewModel : IViewModel + { + /// + /// Gets the abbreviated OID (SHA) of the commit. + /// + string AbbreviatedOid { get; } + + /// + /// Gets the commit author. + /// + ICommitActorViewModel Author { get; } + + /// + /// Gets the commit message header. + /// + string Header { get; } + + /// + /// Gets the OID (SHA) of the commit. + /// + string Oid { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/Documents/IIssueishCommentThreadViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Documents/IIssueishCommentThreadViewModel.cs new file mode 100644 index 0000000000..d3cd6dd86e --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Documents/IIssueishCommentThreadViewModel.cs @@ -0,0 +1,17 @@ +using System; +using System.Threading.Tasks; + +namespace GitHub.ViewModels.Documents +{ + /// + /// A thread of issue or pull request comments. + /// + public interface IIssueishCommentThreadViewModel : ICommentThreadViewModel + { + /// + /// Called by a comment in the thread to close the issue or pull request. + /// + /// The comment requesting the close. + Task CloseOrReopen(ICommentViewModel comment); + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/Documents/IPullRequestPageViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/Documents/IPullRequestPageViewModel.cs new file mode 100644 index 0000000000..5f311fcfa3 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/Documents/IPullRequestPageViewModel.cs @@ -0,0 +1,42 @@ +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.Documents +{ + /// + /// View model for displaying a pull request in a document window. + /// + public interface IPullRequestPageViewModel : IPullRequestViewModelBase + { + /// + /// Gets the number of commits in the pull request. + /// + int CommitCount { get; } + + /// + /// Gets the pull request's timeline. + /// + IReadOnlyList Timeline { get; } + + /// + /// Gets a command that will open a commit in Team Explorer. + /// + ReactiveCommand ShowCommit { get; } + + /// + /// Initializes the view model with data. + /// + /// The repository to which the pull request belongs. + /// The local repository. + /// The currently logged in user. + /// The pull request model. + Task InitializeAsync( + RemoteRepositoryModel repository, + LocalRepositoryModel localRepository, + ActorModel currentUser, + PullRequestDetailModel model); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INoRemoteOriginViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INoRemoteOriginViewModel.cs new file mode 100644 index 0000000000..9d9c9b08b9 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/INoRemoteOriginViewModel.cs @@ -0,0 +1,16 @@ +using System.Reactive; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// Defines the view model for the "No Origin Remote" view in the GitHub pane. + /// + public interface INoRemoteOriginViewModel : IPanePageViewModel + { + /// + /// Gets a command that will allow the user to rename remotes. + /// + ReactiveCommand EditRemotes { get; } + } +} diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestAnnotationItemViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestAnnotationItemViewModel.cs new file mode 100644 index 0000000000..98e60121c4 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestAnnotationItemViewModel.cs @@ -0,0 +1,37 @@ +using System.Reactive; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// The viewmodel for a single annotation item in a list + /// + public interface IPullRequestAnnotationItemViewModel + { + /// + /// Gets the annotation model. + /// + CheckRunAnnotationModel Annotation { get; } + + /// + /// Gets a formatted descriptor of the line(s) the annotation is about. + /// + string LineDescription { get; } + + /// + /// Gets or sets a flag to control the expanded state. + /// + bool IsExpanded { get; set; } + + /// + /// Gets a flag which indicates this annotation item is from a file changed in this pull request. + /// + bool IsFileInPullRequest { get; } + + /// + /// Gets a command which opens the annotation in the diff view. + /// + ReactiveCommand OpenAnnotation { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestAnnotationsViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestAnnotationsViewModel.cs new file mode 100644 index 0000000000..9f4ca12e45 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestAnnotationsViewModel.cs @@ -0,0 +1,91 @@ +using System.Collections.Generic; +using System.Reactive; +using System.Threading.Tasks; +using GitHub.Models; +using ReactiveUI; +using ReactiveUI.Legacy; + +namespace GitHub.ViewModels.GitHubPane +{ + /// + /// A viewmodel which displays a list of annotations for a pull request's check run. + /// + public interface IPullRequestAnnotationsViewModel : IPanePageViewModel + { + /// + /// Gets the local repository. + /// + LocalRepositoryModel LocalRepository { get; } + + /// + /// Gets the owner of the remote repository that contains the pull request. + /// + /// + /// The remote repository may be different from the local repository if the local + /// repository is a fork and the user is viewing pull requests from the parent repository. + /// + string RemoteRepositoryOwner { get; } + + /// + /// Gets the number of the pull request. + /// + int PullRequestNumber { get; } + + /// + /// Gets the title of the pull request. + /// + string PullRequestTitle { get; } + + /// + /// Gets the id of the check run. + /// + string CheckRunId { get; } + + /// + /// Gets the name of the check run. + /// + string CheckRunName { get; } + + /// + /// Gets a command which navigates to the parent pull request. + /// + ReactiveCommand NavigateToPullRequest { get; } + + /// + /// Name of the Check Suite. + /// + string CheckSuiteName { get; } + + /// + /// Summary of the Check Run + /// + string CheckRunSummary { get; } + + /// + /// Text of the Check Run + /// + string CheckRunText { get; } + + /// + /// Gets a dictionary of annotations by file path. + /// + IReadOnlyDictionary AnnotationsDictionary { get; } + + /// + /// Initializes the view model. + /// + /// The local repository. + /// The connection to the repository host. + /// The pull request's repository owner. + /// The pull request's repository name. + /// The pull request's number. + /// The pull request's check run id. + Task InitializeAsync( + LocalRepositoryModel localRepository, + IConnection connection, + string owner, + string repo, + int pullRequestNumber, + string checkRunId); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs index 1a9d658a39..d24a9c7212 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCheckViewModel.cs @@ -1,6 +1,5 @@ using System; using System.Reactive; -using System.Windows.Media.Imaging; using GitHub.Models; using ReactiveUI; @@ -12,30 +11,44 @@ namespace GitHub.ViewModels.GitHubPane public interface IPullRequestCheckViewModel: IViewModel { /// - /// The title of the Status/Check + /// The title of the Status/Check. /// string Title { get; } /// - /// The description of the Status/Check + /// The description of the Status/Check. /// string Description { get; } /// - /// The status of the Status/Check + /// The status of the Status/Check. /// PullRequestCheckStatus Status { get; } /// - /// The url where more information about the Status/Check can be found + /// The url where more information about the Status/Check can be found. /// Uri DetailsUrl { get; } /// - /// A command that opens the DetailsUrl in a browser + /// A command that opens the DetailsUrl in a browser. /// - ReactiveCommand OpenDetailsUrl { get; } + + /// + /// Gets the type of check run, Status/Check. + /// + PullRequestCheckType CheckType { get; } + + /// + /// Gets the id of the check run. + /// + string CheckRunId { get; } + + /// + /// Gets a flag to show this check run has annotations. + /// + bool HasAnnotations { get; } } public enum PullRequestCheckStatus diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCreationViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCreationViewModel.cs index 444c5bd191..fb201f3ab7 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCreationViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestCreationViewModel.cs @@ -4,6 +4,7 @@ using ReactiveUI; using System.Threading.Tasks; using System.Reactive; +using GitHub.Services; namespace GitHub.ViewModels.GitHubPane { @@ -16,6 +17,7 @@ public interface IPullRequestCreationViewModel : IPanePageViewModel ReactiveCommand Cancel { get; } string PRTitle { get; set; } ReactivePropertyValidator TitleValidator { get; } + IAutoCompleteAdvisor AutoCompleteAdvisor { get; } Task InitializeAsync(LocalRepositoryModel repository, IConnection connection); } diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs index c10c6b2376..0e4616fb67 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestDetailViewModel.cs @@ -5,6 +5,7 @@ using GitHub.Models; using GitHub.Services; using ReactiveUI; +using ReactiveUI.Legacy; namespace GitHub.ViewModels.GitHubPane { @@ -62,7 +63,7 @@ public interface IPullRequestUpdateState } /// - /// Represents a view model for displaying details of a pull request. + /// A view model which displays the details of a pull request. /// public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowser { @@ -165,6 +166,16 @@ public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowse /// ReactiveCommand Push { get; } + /// + /// Sync submodules for PR branch. + /// + ReactiveCommand SyncSubmodules { get; } + + /// + /// Gets a command that opens the pull request conversation in a document pane. + /// + ReactiveCommand OpenConversation { get; } + /// /// Gets a command that opens the pull request on GitHub. /// @@ -176,7 +187,12 @@ public interface IPullRequestDetailViewModel : IPanePageViewModel, IOpenInBrowse ReactiveCommand ShowReview { get; } /// - /// Gets the latest pull request Checks & Statuses + /// Gets a command that navigates to a pull request's check run annotation list. + /// + ReactiveCommand ShowAnnotations { get; } + + /// + /// Gets the latest pull request checks & statuses. /// IReadOnlyList Checks { get; } diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFileNode.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFileNode.cs index 2eaac8c82b..c9d1b55b8e 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFileNode.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFileNode.cs @@ -33,5 +33,20 @@ public interface IPullRequestFileNode : IPullRequestChangeNode /// Gets the number of review comments on the file. /// int CommentCount { get; } + + /// + /// Gets or sets the number of annotation notices on the file. + /// + int AnnotationNoticeCount { get; } + + /// + /// Gets or sets the number of annotation errors on the file. + /// + int AnnotationWarningCount { get; } + + /// + /// Gets or sets the number of annotation failures on the file. + /// + int AnnotationFailureCount { get; } } } \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs index 7f2eda69c8..ebef31bf39 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestFilesViewModel.cs @@ -51,6 +51,24 @@ public interface IPullRequestFilesViewModel : IViewModel, IDisposable /// ReactiveCommand OpenFirstComment { get; } + /// + /// Gets a command that opens the first annotation notice for a in + /// the diff viewer. + /// + ReactiveCommand OpenFirstAnnotationNotice { get; } + + /// + /// Gets a command that opens the first annotation warning for a in + /// the diff viewer. + /// + ReactiveCommand OpenFirstAnnotationWarning { get; } + + /// + /// Gets a command that opens the first annotation failure for a in + /// the diff viewer. + /// + ReactiveCommand OpenFirstAnnotationFailure { get; } + /// /// Initializes the view model. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs index f8fa89c351..9f6758243b 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListItemViewModel.cs @@ -32,6 +32,21 @@ public interface IPullRequestListItemViewModel : IIssueListItemViewModelBase /// /// Gets the pull request checks and statuses summary /// - PullRequestChecksState Checks { get; } + PullRequestChecksSummaryState ChecksSummary { get; } + + /// + /// Gets the number of pending checks and statuses + /// + int ChecksPendingCount { get; } + + /// + /// Gets the number of successful checks and statuses + /// + int ChecksSuccessCount { get; } + + /// + /// Gets the number of erroneous checks and statuses + /// + int ChecksErrorCount { get; } } } diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListViewModel.cs index e347329812..802a0083ad 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestListViewModel.cs @@ -15,7 +15,7 @@ public interface IPullRequestListViewModel : IIssueListViewModelBase, IOpenInBro ReactiveCommand CreatePullRequest { get; } /// - /// Gets a command that opens pull request item on GitHub. + /// Gets a command that opens the pull request item on GitHub. /// ReactiveCommand OpenItemInBrowser { get; } } diff --git a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs index e0be2242d9..6d6137050a 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/GitHubPane/IPullRequestReviewAuthoringViewModel.cs @@ -3,6 +3,7 @@ using System.Reactive; using System.Threading.Tasks; using GitHub.Models; +using GitHub.Services; using ReactiveUI; namespace GitHub.ViewModels.GitHubPane @@ -87,6 +88,11 @@ public interface IPullRequestReviewAuthoringViewModel : IPanePageViewModel, IDis /// ReactiveCommand Cancel { get; } + /// + /// Provides an AutoCompleteAdvisor. + /// + IAutoCompleteAdvisor AutoCompleteAdvisor { get; } + /// /// Initializes the view model for creating a new review. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/ICommentThreadViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ICommentThreadViewModel.cs index 31c24d15a5..5c520519cd 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/ICommentThreadViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/ICommentThreadViewModel.cs @@ -10,12 +10,7 @@ namespace GitHub.ViewModels public interface ICommentThreadViewModel : IViewModel { /// - /// Gets the comments in the thread. - /// - IReadOnlyReactiveList Comments { get; } - - /// - /// Gets the current user under whos account new comments will be created. + /// Gets the current user under whose account new comments will be created. /// IActorViewModel CurrentUser { get; } diff --git a/src/GitHub.Exports.Reactive/ViewModels/ICommentViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ICommentViewModel.cs index 5914b0ddbd..be6d97e13b 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/ICommentViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/ICommentViewModel.cs @@ -1,5 +1,6 @@ using System; using System.Reactive; +using GitHub.Services; using ReactiveUI; namespace GitHub.ViewModels @@ -11,8 +12,8 @@ public enum CommentEditState Placeholder, } - /// - /// View model for an issue or pull request comment. + /// + /// View model for an issue, pull request or pull request review comment. /// public interface ICommentViewModel : IViewModel { @@ -63,7 +64,12 @@ public interface ICommentViewModel : IViewModel bool IsSubmitting { get; } /// - /// Gets a value indicating whether the comment can be edited or deleted by the current user + /// Gets a value indicating whether the comment edit state can be canceled. + /// + bool CanCancel { get; } + + /// + /// Gets a value indicating whether the comment can be edited or deleted by the current user. /// bool CanDelete { get; } @@ -72,6 +78,14 @@ public interface ICommentViewModel : IViewModel /// DateTimeOffset CreatedAt { get; } + /// + /// Gets the caption for the "Commit" button. + /// + /// + /// This will be "Comment" when editing a new comment and "Update" when editing an existing comment. + /// + string CommitCaption { get; } + /// /// Gets the thread that the comment is a part of. /// @@ -106,5 +120,10 @@ public interface ICommentViewModel : IViewModel /// Deletes a comment. /// ReactiveCommand Delete { get; } + + /// + /// Provides an AutoCompleteAdvisor. + /// + IAutoCompleteAdvisor AutoCompleteAdvisor { get; } } } \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/ICommitActorViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/ICommitActorViewModel.cs new file mode 100644 index 0000000000..a736aac349 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/ICommitActorViewModel.cs @@ -0,0 +1,9 @@ +namespace GitHub.ViewModels +{ + public interface ICommitActorViewModel : IActorViewModel + { + string Email { get; } + string Name { get; } + bool HasLogin { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/IInlineAnnotationViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IInlineAnnotationViewModel.cs new file mode 100644 index 0000000000..1ed533c306 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/IInlineAnnotationViewModel.cs @@ -0,0 +1,15 @@ +using GitHub.Models; + +namespace GitHub.ViewModels +{ + /// + /// A view model that represents a single inline annotation. + /// + public interface IInlineAnnotationViewModel + { + /// + /// Gets the inline annotation model. + /// + InlineAnnotationModel Model { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/IIssueishViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IIssueishViewModel.cs new file mode 100644 index 0000000000..fed3456d09 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/IIssueishViewModel.cs @@ -0,0 +1,53 @@ +using System; +using System.Reactive; +using GitHub.Models; +using ReactiveUI; + +namespace GitHub.ViewModels +{ + /// + /// Base interface for issue and pull request view models. + /// + public interface IIssueishViewModel : IViewModel + { + /// + /// Gets the GraphQL ID for the issue or pull request. + /// + string Id { get; } + + /// + /// Gets the issue or pull request author. + /// + IActorViewModel Author { get; } + + /// + /// Gets the issue or pull request body. + /// + string Body { get; } + + /// + /// Gets the issue or pull request number. + /// + int Number { get; } + + /// + /// Gets the repository that the issue or pull request comes from. + /// + RemoteRepositoryModel Repository { get; } + + /// + /// Gets the issue or pull request title. + /// + string Title { get; } + + /// + /// Gets the URL of the issue or pull request. + /// + Uri WebUrl { get; } + + /// + /// Gets a command which opens the issue or pull request in a browser. + /// + ReactiveCommand OpenOnGitHub { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/IPullRequestReviewCommentThreadViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IPullRequestReviewCommentThreadViewModel.cs index 0a74e78b56..b669927a07 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/IPullRequestReviewCommentThreadViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/IPullRequestReviewCommentThreadViewModel.cs @@ -1,6 +1,8 @@ -using System.Threading.Tasks; +using System.Collections.Generic; +using System.Threading.Tasks; using GitHub.Models; using GitHub.Services; +using ReactiveUI; namespace GitHub.ViewModels { @@ -9,6 +11,11 @@ namespace GitHub.ViewModels /// public interface IPullRequestReviewCommentThreadViewModel : ICommentThreadViewModel { + /// + /// Gets the comments in the thread. + /// + IReadOnlyReactiveList Comments { get; } + /// /// Gets the current pull request review session. /// @@ -29,6 +36,11 @@ public interface IPullRequestReviewCommentThreadViewModel : ICommentThreadViewMo /// DiffSide Side { get; } + /// + /// Gets a value indicating whether comment thread has been marked as resolved by a user. + /// + bool IsResolved { get; } + /// /// Gets a value indicating whether the thread is a new thread being authored, that is not /// yet present on the server. @@ -48,10 +60,9 @@ public interface IPullRequestReviewCommentThreadViewModel : ICommentThreadViewMo /// The file that the comment is on. /// The thread. /// - /// Whether to add a placeholder comment at the end of the thread. + /// Whether to add a placeholder comment at the end of the thread. /// - Task InitializeAsync( - IPullRequestSession session, + Task InitializeAsync(IPullRequestSession session, IPullRequestSessionFile file, IInlineCommentThreadModel thread, bool addPlaceholder); @@ -64,8 +75,7 @@ Task InitializeAsync( /// The 0-based line number of the thread. /// The side of the diff. /// Whether to start the placeholder in edit state. - Task InitializeNewAsync( - IPullRequestSession session, + Task InitializeNewAsync(IPullRequestSession session, IPullRequestSessionFile file, int lineNumber, DiffSide side, diff --git a/src/GitHub.Exports.Reactive/ViewModels/IPullRequestReviewCommentViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/IPullRequestReviewCommentViewModel.cs index b05ab5c0f7..8212fdba3e 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/IPullRequestReviewCommentViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/IPullRequestReviewCommentViewModel.cs @@ -16,15 +16,6 @@ public interface IPullRequestReviewCommentViewModel : ICommentViewModel /// bool CanStartReview { get; } - /// - /// Gets the caption for the "Commit" button. - /// - /// - /// This will be "Add a single comment" when not in review mode and "Add review comment" - /// when in review mode. - /// - string CommitCaption { get; } - /// /// Gets a value indicating whether this comment is part of a pending pull request review. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/IPullRequestViewModelBase.cs b/src/GitHub.Exports.Reactive/ViewModels/IPullRequestViewModelBase.cs new file mode 100644 index 0000000000..ae197e1c61 --- /dev/null +++ b/src/GitHub.Exports.Reactive/ViewModels/IPullRequestViewModelBase.cs @@ -0,0 +1,30 @@ +using GitHub.Models; + +namespace GitHub.ViewModels +{ + /// + /// Base class for pull request view models. + /// + public interface IPullRequestViewModelBase : IIssueishViewModel + { + /// + /// Gets the local repository. + /// + LocalRepositoryModel LocalRepository { get; } + + /// + /// Gets the pull request state. + /// + PullRequestState State { get; } + + /// + /// Gets a the pull request's source (head) branch display. + /// + string SourceBranchDisplayName { get; } + + /// + /// Gets a the pull request's target (base) branch display. + /// + string TargetBranchDisplayName { get; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryForm.cs b/src/GitHub.Exports.Reactive/ViewModels/IRepositoryForm.cs index 6047d41daf..efb15369e7 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/IRepositoryForm.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/IRepositoryForm.cs @@ -41,18 +41,5 @@ public interface IRepositoryForm : IViewModel /// Indicates whether the created repository should be private or not. /// bool KeepPrivate { get; set; } - - /// - /// Indicates whether the user can create a private repository. This is false if the user is not a paid - /// account or if the user has run out of repositories for their current plan. - /// - bool CanKeepPrivate { get; } - bool ShowUpgradeToMicroPlanWarning { get; } - bool ShowUpgradePlanWarning { get; } - - /// - /// Command that opens a browser to a page for upgrading the user's plan. - /// - ICommand UpgradeAccountPlan { get; } } } diff --git a/src/GitHub.Exports.Reactive/ViewModels/TeamExplorer/IRepositoryPublishViewModel.cs b/src/GitHub.Exports.Reactive/ViewModels/TeamExplorer/IRepositoryPublishViewModel.cs index ff1d028e98..efd0ef6e9b 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/TeamExplorer/IRepositoryPublishViewModel.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/TeamExplorer/IRepositoryPublishViewModel.cs @@ -19,6 +19,11 @@ public interface IRepositoryPublishViewModel : IViewModel, IRepositoryForm /// ReactiveCommand PublishRepository { get; } + /// + /// Command that shows login dialog. + /// + ReactiveCommand LoginAsDifferentUser { get; } + /// /// Determines whether the host combo box is visible. Only true if the user is logged into more than one host. /// diff --git a/src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs b/src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs index be2bb1780a..6cae3c4e06 100644 --- a/src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs +++ b/src/GitHub.Exports.Reactive/ViewModels/ViewModelBase.cs @@ -1,6 +1,8 @@ using System; -using GitHub.UI; +using System.Reactive; +using GitHub.Logging; using ReactiveUI; +using Serilog; namespace GitHub.ViewModels { @@ -13,5 +15,15 @@ namespace GitHub.ViewModels /// public abstract class ViewModelBase : ReactiveObject, IViewModel { + static readonly ILogger logger = LogManager.ForContext(); + + static ViewModelBase() + { + // We don't really have a better place to hook this up as we don't want to force-load + // rx on package load. + RxApp.DefaultExceptionHandler = Observer.Create( + ex => logger.Error(ex, "Unhandled rxui error"), + ex => logger.Error(ex, "Unhandled rxui error")); + } } } diff --git a/src/GitHub.Exports/Commands/IOpenIssueishDocumentCommand.cs b/src/GitHub.Exports/Commands/IOpenIssueishDocumentCommand.cs new file mode 100644 index 0000000000..4f767cc503 --- /dev/null +++ b/src/GitHub.Exports/Commands/IOpenIssueishDocumentCommand.cs @@ -0,0 +1,11 @@ +using System; + +namespace GitHub.Commands +{ + /// + /// Opens an issue or pull request in a new document window. + /// + public interface IOpenIssueishDocumentCommand : IVsCommand + { + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Commands/OpenIssueishParams.cs b/src/GitHub.Exports/Commands/OpenIssueishParams.cs new file mode 100644 index 0000000000..2c3d7e6ea2 --- /dev/null +++ b/src/GitHub.Exports/Commands/OpenIssueishParams.cs @@ -0,0 +1,23 @@ +using GitHub.Primitives; + +namespace GitHub.Commands +{ + public class OpenIssueishParams + { + public OpenIssueishParams( + HostAddress address, + string owner, + string repository, + int number) + { + Address = address; + Owner = owner; + Repository = repository; + Number = number; + } + public HostAddress Address { get; } + public string Owner { get; } + public string Repository { get; } + public int Number { get; } + } +} diff --git a/src/GitHub.Exports/ExceptionExtensions.cs b/src/GitHub.Exports/ExceptionExtensions.cs index a9f54c6eff..4afbb3011a 100644 --- a/src/GitHub.Exports/ExceptionExtensions.cs +++ b/src/GitHub.Exports/ExceptionExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Linq; using Octokit; namespace GitHub.Extensions @@ -9,8 +10,7 @@ public static class ApiExceptionExtensions public static bool IsGitHubApiException(this Exception ex) { var apiex = ex as ApiException; - return apiex?.HttpResponse?.Headers.ContainsKey(GithubHeader) ?? false; + return apiex?.HttpResponse?.Headers.Keys.Contains(GithubHeader, StringComparer.OrdinalIgnoreCase) ?? false; } } - } diff --git a/src/GitHub.Exports/Exports/ExportMetadata.cs b/src/GitHub.Exports/Exports/ExportMetadata.cs index bdab361eb2..74ecf29ba3 100644 --- a/src/GitHub.Exports/Exports/ExportMetadata.cs +++ b/src/GitHub.Exports/Exports/ExportMetadata.cs @@ -21,7 +21,8 @@ public enum LinkType { Unknown, Blob, - Blame + Blame, + Repository } /// diff --git a/src/GitHub.Exports/ExtensionInformation.cs b/src/GitHub.Exports/ExtensionInformation.cs new file mode 100644 index 0000000000..0e19dd1d41 --- /dev/null +++ b/src/GitHub.Exports/ExtensionInformation.cs @@ -0,0 +1,9 @@ +namespace GitHub +{ + public static class ExtensionInformation + { + // HACK: For some reason ThisAssembly.AssemblyFileVersion can't be referenced + // directly from inside GitHub.VisualStudio. + public const string Version = ThisAssembly.AssemblyFileVersion; + } +} diff --git a/src/GitHub.Exports/Extensions/PropertyNotifierExtensions.cs b/src/GitHub.Exports/Extensions/PropertyNotifierExtensions.cs index 899e9aa407..be3ddeef3a 100644 --- a/src/GitHub.Exports/Extensions/PropertyNotifierExtensions.cs +++ b/src/GitHub.Exports/Extensions/PropertyNotifierExtensions.cs @@ -1,5 +1,7 @@ using System.Runtime.CompilerServices; +#pragma warning disable CA1030 // Use events where appropriate + namespace GitHub.VisualStudio.Helpers { public static class PropertyNotifierExtensions diff --git a/src/GitHub.Exports/GitHub.Exports.csproj b/src/GitHub.Exports/GitHub.Exports.csproj index 6de77d730b..b6fa2fc7f5 100644 --- a/src/GitHub.Exports/GitHub.Exports.csproj +++ b/src/GitHub.Exports/GitHub.Exports.csproj @@ -23,18 +23,10 @@ - - - - + + - - - - - - diff --git a/src/GitHub.Exports/GlobalSuppressions.cs b/src/GitHub.Exports/GlobalSuppressions.cs index d5fcc9b07a..e69de29bb2 100644 Binary files a/src/GitHub.Exports/GlobalSuppressions.cs and b/src/GitHub.Exports/GlobalSuppressions.cs differ diff --git a/src/GitHub.Exports/Helpers/INotifyPropertySource.cs b/src/GitHub.Exports/Helpers/INotifyPropertySource.cs index 23d8dbe8e2..5f6b15de4a 100644 --- a/src/GitHub.Exports/Helpers/INotifyPropertySource.cs +++ b/src/GitHub.Exports/Helpers/INotifyPropertySource.cs @@ -1,4 +1,6 @@ -namespace GitHub.VisualStudio.Helpers +#pragma warning disable CA1030 // Use events where appropriate + +namespace GitHub.VisualStudio.Helpers { public interface INotifyPropertySource { diff --git a/src/GitHub.Exports/Helpers/ThreadingHelper.cs b/src/GitHub.Exports/Helpers/ThreadingHelper.cs deleted file mode 100644 index cb678173b6..0000000000 --- a/src/GitHub.Exports/Helpers/ThreadingHelper.cs +++ /dev/null @@ -1,115 +0,0 @@ -using System.Threading.Tasks; -using ThreadHelper = Microsoft.VisualStudio.Shell.ThreadHelper; -using GitHub.Extensions; -using System.Runtime.CompilerServices; -using System; -using System.Threading; -using System.Windows; -using static Microsoft.VisualStudio.Threading.JoinableTaskFactory; -using static Microsoft.VisualStudio.Threading.AwaitExtensions; -using System.Windows.Threading; - -namespace GitHub.Helpers -{ - public interface IAwaitable - { - IAwaiter GetAwaiter(); - } - - public interface IAwaiter : INotifyCompletion - { - bool IsCompleted { get; } - void GetResult(); - } - - public static class ThreadingHelper - { - public static bool InUIThread => Guard.InUnitTestRunner ? true : Application.Current.Dispatcher.CheckAccess(); - - /// - /// Gets the Dispatcher for the main thread. - /// - public static Dispatcher MainThreadDispatcher => Application.Current.Dispatcher; - - /// - /// Switch to the UI thread using ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync - /// Auto-disables switching when running in unit test mode - /// - /// - public static IAwaitable SwitchToMainThreadAsync() - { - return Guard.InUnitTestRunner ? - new AwaitableWrapper() : - new AwaitableWrapper(ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync()); - } - - /// - /// Switch to a thread pool background thread if the current thread isn't one, otherwise does nothing - /// Auto-disables switching when running in unit test mode - /// - /// - /// - public static IAwaitable SwitchToPoolThreadAsync(TaskScheduler scheduler = null) - { - return Guard.InUnitTestRunner ? - new AwaitableWrapper() : - new AwaitableWrapper(scheduler ?? TaskScheduler.Default); - } - - class AwaitableWrapper : IAwaitable - { - Func getAwaiter; - - public AwaitableWrapper() - { - getAwaiter = () => new AwaiterWrapper(); - } - - public AwaitableWrapper(MainThreadAwaitable awaitable) - { - getAwaiter = () => new AwaiterWrapper(awaitable.GetAwaiter()); - } - - public AwaitableWrapper(TaskScheduler scheduler) - { - getAwaiter = () => new AwaiterWrapper(new TaskSchedulerAwaiter(scheduler)); - } - - public IAwaiter GetAwaiter() => getAwaiter(); - } - - class AwaiterWrapper : IAwaiter - { - Func isCompleted; - Action onCompleted; - Action getResult; - - public AwaiterWrapper() - { - isCompleted = () => true; - onCompleted = c => c(); - getResult = () => { }; - } - - public AwaiterWrapper(MainThreadAwaiter awaiter) - { - isCompleted = () => awaiter.IsCompleted; - onCompleted = c => awaiter.OnCompleted(c); - getResult = () => awaiter.GetResult(); - } - - public AwaiterWrapper(TaskSchedulerAwaiter awaiter) - { - isCompleted = () => awaiter.IsCompleted; - onCompleted = c => awaiter.OnCompleted(c); - getResult = () => awaiter.GetResult(); - } - - public bool IsCompleted => isCompleted(); - - public void OnCompleted(Action continuation) => onCompleted(continuation); - - public void GetResult() => getResult(); - } - } -} diff --git a/src/GitHub.Exports/Models/AnnotationModel.cs b/src/GitHub.Exports/Models/AnnotationModel.cs index c986fc8aa3..8981b9d719 100644 --- a/src/GitHub.Exports/Models/AnnotationModel.cs +++ b/src/GitHub.Exports/Models/AnnotationModel.cs @@ -6,24 +6,19 @@ public class CheckRunAnnotationModel { /// - /// The path to the file that this annotation was made on. - /// - public string BlobUrl { get; set; } - - /// - /// The starting line number (1 indexed). + /// The starting 1-based line number (1 indexed). /// public int StartLine { get; set; } /// - /// The ending line number (1 indexed). + /// The ending 1-based line number (1 indexed). /// public int EndLine { get; set; } /// /// The path that this annotation was made on. /// - public string Filename { get; set; } + public string Path { get; set; } /// /// The annotation's message. @@ -38,11 +33,6 @@ public class CheckRunAnnotationModel /// /// The annotation's severity level. /// - public CheckAnnotationLevel? AnnotationLevel { get; set; } - - /// - /// Additional information about the annotation. - /// - public string RawDetails { get; set; } + public CheckAnnotationLevel AnnotationLevel { get; set; } } } \ No newline at end of file diff --git a/src/GitHub.Exports/Models/BranchModel.cs b/src/GitHub.Exports/Models/BranchModel.cs index e72280f5c2..06e0203a23 100644 --- a/src/GitHub.Exports/Models/BranchModel.cs +++ b/src/GitHub.Exports/Models/BranchModel.cs @@ -7,12 +7,13 @@ namespace GitHub.Models public class BranchModel : ICopyable, IEquatable, IComparable { - public BranchModel(string name, RepositoryModel repo, string sha, bool isTracking, string trackedSha) : + public BranchModel(string name, RepositoryModel repo, string sha, bool isTracking, string trackedSha, string trackedRemoteName) : this(name, repo) { IsTracking = isTracking; Sha = sha; TrackedSha = trackedSha; + TrackedRemoteName = trackedRemoteName; } public BranchModel(string name, RepositoryModel repo) @@ -32,6 +33,7 @@ public BranchModel(string name, RepositoryModel repo) public string DisplayName { get; set; } public string Sha { get; private set; } public string TrackedSha { get; private set; } + public string TrackedRemoteName { get; private set; } #region Equality things public void CopyFrom(BranchModel other) diff --git a/src/GitHub.Exports/Models/CheckConclusionState.cs b/src/GitHub.Exports/Models/CheckConclusionState.cs index 3659c3dadb..a8f24d076b 100644 --- a/src/GitHub.Exports/Models/CheckConclusionState.cs +++ b/src/GitHub.Exports/Models/CheckConclusionState.cs @@ -8,5 +8,7 @@ public enum CheckConclusionState Failure, Success, Neutral, + Skipped, + Stale } } \ No newline at end of file diff --git a/src/GitHub.Exports/Models/CheckRunModel.cs b/src/GitHub.Exports/Models/CheckRunModel.cs index a25335c8bb..de6ec46d9d 100644 --- a/src/GitHub.Exports/Models/CheckRunModel.cs +++ b/src/GitHub.Exports/Models/CheckRunModel.cs @@ -8,7 +8,14 @@ namespace GitHub.Models /// public class CheckRunModel { - /// The conclusion of the check run. + /// + /// The id of a Check Run. + /// + public string Id { get; set; } + + /// + /// The conclusion of the check run. + /// public CheckConclusionState? Conclusion { get; set; } /// @@ -21,7 +28,14 @@ public class CheckRunModel /// public DateTimeOffset? CompletedAt { get; set; } - /// The name of the check for this check run. + /// + /// The check run's annotations. + /// + public List Annotations { get; set; } + + /// + /// The name of the check for this check run. + /// public string Name { get; set; } /// @@ -33,5 +47,10 @@ public class CheckRunModel /// The summary of a Check Run. /// public string Summary { get; set; } + + /// + /// The detail of a Check Run. + /// + public string Text { get; set; } } } \ No newline at end of file diff --git a/src/GitHub.Exports/Models/CheckSuiteModel.cs b/src/GitHub.Exports/Models/CheckSuiteModel.cs index c6e82cc1be..43ad354910 100644 --- a/src/GitHub.Exports/Models/CheckSuiteModel.cs +++ b/src/GitHub.Exports/Models/CheckSuiteModel.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; namespace GitHub.Models @@ -8,9 +8,16 @@ namespace GitHub.Models /// public class CheckSuiteModel { + /// + /// The head sha of a Check Suite. + /// + public string HeadSha { get; set; } + /// /// The check runs associated with a check suite. /// public List CheckRuns { get; set; } + + public string ApplicationName { get; set; } } } \ No newline at end of file diff --git a/src/GitHub.Exports/Models/CommentModel.cs b/src/GitHub.Exports/Models/CommentModel.cs index 2b601714dd..902bf5263a 100644 --- a/src/GitHub.Exports/Models/CommentModel.cs +++ b/src/GitHub.Exports/Models/CommentModel.cs @@ -17,14 +17,6 @@ public class CommentModel /// public int DatabaseId { get; set; } - /// - /// Gets the PullRequestId of the comment. - /// - public int PullRequestId { get; set; } - // The GraphQL Api does not allow for deleting of pull request comments. - // REST Api must be used, and PullRequestId is needed to reload the pull request. - // This field should be removed with better GraphQL support. - /// /// Gets the author of the comment. /// diff --git a/src/GitHub.Exports/Models/CommitActorModel.cs b/src/GitHub.Exports/Models/CommitActorModel.cs new file mode 100644 index 0000000000..02280bfbd1 --- /dev/null +++ b/src/GitHub.Exports/Models/CommitActorModel.cs @@ -0,0 +1,23 @@ +namespace GitHub.Models +{ + /// + /// Represents a commit actor (which may or may not have an associated User or Bot). + /// + public class CommitActorModel + { + /// + /// Gets or sets the actor user + /// + public ActorModel User { get; set; } + + /// + /// Gets or sets the actor name + /// + public string Name { get; set; } + + /// + /// Gets or sets the actor email + /// + public string Email { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/Models/CommitMessage.cs b/src/GitHub.Exports/Models/CommitMessage.cs index 70d0034fe7..58ef8cc182 100644 --- a/src/GitHub.Exports/Models/CommitMessage.cs +++ b/src/GitHub.Exports/Models/CommitMessage.cs @@ -39,8 +39,8 @@ public bool Equals(CommitMessage other) return false; } - return string.Equals(Summary, other.Summary) - && string.Equals(Details, other.Details); + return string.Equals(Summary, other.Summary, StringComparison.Ordinal) + && string.Equals(Details, other.Details, StringComparison.Ordinal); } public override bool Equals(object obj) diff --git a/src/GitHub.Exports/Models/CommitModel.cs b/src/GitHub.Exports/Models/CommitModel.cs new file mode 100644 index 0000000000..a0aaad8a83 --- /dev/null +++ b/src/GitHub.Exports/Models/CommitModel.cs @@ -0,0 +1,30 @@ +using System; + +namespace GitHub.Models +{ + /// + /// Holds the details of a commit. + /// + public class CommitModel + { + /// + /// Gets or sets the author of the commit. + /// + public CommitActorModel Author { get; set; } + + /// + /// Gets or sets the abbreviated git object ID for the commit. + /// + public string AbbreviatedOid { get; set; } + + /// + /// Gets or sets the commit headline. + /// + public string MessageHeadline { get; set; } + + /// + /// Gets or sets the git object ID for the commit. + /// + public string Oid { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/DiffUtilities.cs b/src/GitHub.Exports/Models/DiffUtilities.cs index fff85765bd..6d781075a1 100644 --- a/src/GitHub.Exports/Models/DiffUtilities.cs +++ b/src/GitHub.Exports/Models/DiffUtilities.cs @@ -1,10 +1,15 @@ using System; using System.IO; +using System.Globalization; using System.Collections.Generic; using System.Text.RegularExpressions; using System.Diagnostics.CodeAnalysis; using GitHub.Extensions; +#pragma warning disable CA1034 // Nested types should not be visible + +#pragma warning disable CA1034 // Nested types should not be visible + namespace GitHub.Models { public static class DiffUtilities @@ -37,8 +42,8 @@ public static IEnumerable ParseFragment(string diff) chunk = new DiffChunk { - OldLineNumber = oldLine = int.Parse(headerMatch.Groups[1].Value), - NewLineNumber = newLine = int.Parse(headerMatch.Groups[2].Value), + OldLineNumber = oldLine = int.Parse(headerMatch.Groups[1].Value, CultureInfo.InvariantCulture), + NewLineNumber = newLine = int.Parse(headerMatch.Groups[2].Value, CultureInfo.InvariantCulture), DiffLine = diffLine, }; } diff --git a/src/GitHub.Exports/Models/IPullRequestModel.cs b/src/GitHub.Exports/Models/IPullRequestModel.cs index 4d78de08ab..58672e3eec 100644 --- a/src/GitHub.Exports/Models/IPullRequestModel.cs +++ b/src/GitHub.Exports/Models/IPullRequestModel.cs @@ -4,18 +4,17 @@ namespace GitHub.Models { - /// TODO: A PullRequestState class already exists hence the ugly naming of this. - /// Merge the two when the maintainer workflow has been merged to master. - public enum PullRequestStateEnum + public enum PullRequestState { Open, Closed, Merged, } - public enum PullRequestChecksState + public enum PullRequestChecksSummaryState { None, + Mixed, Pending, Success, Failure @@ -26,7 +25,7 @@ public interface IPullRequestModel : ICopyable, { int Number { get; } string Title { get; } - PullRequestStateEnum State { get; } + PullRequestState State { get; } int CommentCount { get; } int CommitCount { get; } bool IsOpen { get; } diff --git a/src/GitHub.Exports/Models/IssueishDetailModel.cs b/src/GitHub.Exports/Models/IssueishDetailModel.cs new file mode 100644 index 0000000000..39ac56562d --- /dev/null +++ b/src/GitHub.Exports/Models/IssueishDetailModel.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +namespace GitHub.Models +{ + /// + /// Base class for issue and pull request detail models. + /// + public class IssueishDetailModel + { + /// + /// Gets or sets the GraphQL ID of the issue or pull request. + /// + public string Id { get; set; } + + /// + /// Gets or sets the issue or pull request number. + /// + public int Number { get; set; } + + /// + /// Gets or sets the issue or pull request author. + /// + public ActorModel Author { get; set; } + + /// + /// Gets or sets the issue or pull request title. + /// + public string Title { get; set; } + + /// + /// Gets or sets the issue or pull request body. + /// + public string Body { get; set; } + + /// + /// Gets or sets the date/time at which the issue or pull request was last updated. + /// + public DateTimeOffset UpdatedAt { get; set; } + + /// + /// Gets or sets the comments on the issue or pull request. + /// + public IReadOnlyList Comments { get; set; } + + /// + /// Gets or sets the number of comments on the issue or pull request. + /// + public int CommentCount { get; set; } + } +} diff --git a/src/GitHub.Exports/Models/LocalRepositoryModel.cs b/src/GitHub.Exports/Models/LocalRepositoryModel.cs index edaf63a559..f6f491c722 100644 --- a/src/GitHub.Exports/Models/LocalRepositoryModel.cs +++ b/src/GitHub.Exports/Models/LocalRepositoryModel.cs @@ -2,6 +2,8 @@ using System.Diagnostics; using System.Globalization; using System.ComponentModel; +using System.Collections.Generic; +using GitHub.Primitives; namespace GitHub.Models { @@ -22,6 +24,14 @@ public string LocalPath get; set; } + /// + /// True if repository has remotes but none are named "origin". + /// + public bool HasRemotesButNoOrigin + { + get; set; + } + /// /// Note: We don't consider CloneUrl a part of the hash code because it can change during the lifetime /// of a repository. Equals takes care of any hash collisions because of this @@ -45,9 +55,9 @@ public bool Equals(LocalRepositoryModel other) if (ReferenceEquals(this, other)) return true; return other != null && - string.Equals(Name, other.Name) && - string.Equals(Owner, other.Owner) && - string.Equals(CloneUrl, other.CloneUrl) && + string.Equals(Name, other.Name, StringComparison.Ordinal) && + string.Equals(Owner, other.Owner, StringComparison.Ordinal) && + string.Equals(CloneUrl, other.CloneUrl, StringComparison.Ordinal) && string.Equals(LocalPath?.TrimEnd('\\'), other.LocalPath?.TrimEnd('\\'), StringComparison.CurrentCultureIgnoreCase); } diff --git a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckType.cs b/src/GitHub.Exports/Models/PullRequestCheckType.cs similarity index 67% rename from src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckType.cs rename to src/GitHub.Exports/Models/PullRequestCheckType.cs index f5cc9dbc20..d088320b53 100644 --- a/src/GitHub.App/ViewModels/GitHubPane/PullRequestCheckType.cs +++ b/src/GitHub.Exports/Models/PullRequestCheckType.cs @@ -1,4 +1,4 @@ -namespace GitHub.ViewModels.GitHubPane +namespace GitHub.Models { public enum PullRequestCheckType { diff --git a/src/GitHub.Exports/Models/PullRequestDetailModel.cs b/src/GitHub.Exports/Models/PullRequestDetailModel.cs index 975c6511d2..28cf560a0b 100644 --- a/src/GitHub.Exports/Models/PullRequestDetailModel.cs +++ b/src/GitHub.Exports/Models/PullRequestDetailModel.cs @@ -6,37 +6,12 @@ namespace GitHub.Models /// /// Holds the details of a Pull Request. /// - public class PullRequestDetailModel + public class PullRequestDetailModel : IssueishDetailModel { - /// - /// Gets or sets the GraphQL ID of the pull request. - /// - public string Id { get; set; } - - /// - /// Gets or sets the pull request number. - /// - public int Number { get; set; } - - /// - /// Gets or sets the pull request author. - /// - public ActorModel Author { get; set; } - - /// - /// Gets or sets the pull request title. - /// - public string Title { get; set; } - /// /// Gets or sets the pull request state (open, closed, merged). /// - public PullRequestStateEnum State { get; set; } - - /// - /// Gets or sets the pull request body markdown. - /// - public string Body { get; set; } + public PullRequestState State { get; set; } /// /// Gets or sets the name of the base branch (e.g. "master"). @@ -67,17 +42,22 @@ public class PullRequestDetailModel /// Gets or sets the owner login of the repository containing the head branch. /// public string HeadRepositoryOwner { get; set; } - - /// - /// Gets or sets the date/time at which the pull request was last updated. - /// - public DateTimeOffset UpdatedAt { get; set; } /// /// Gets or sets a collection of files changed by the pull request. /// public IReadOnlyList ChangedFiles { get; set; } + /// + /// Gets or sets a collection of pull request Checks Suites. + /// + public IReadOnlyList CheckSuites { get; set; } + + /// + /// Gets or sets a collection of pull request Statuses + /// + public IReadOnlyList Statuses { get; set; } + /// /// Gets or sets a collection of pull request reviews. /// @@ -93,13 +73,8 @@ public class PullRequestDetailModel public IReadOnlyList Threads { get; set; } /// - /// Gets or sets a collection of pull request Checks Suites - /// - public IReadOnlyList CheckSuites { get; set; } - - /// - /// Gets or sets a collection of pull request Statuses + /// Gets or sets the pull request timeline entries. /// - public IReadOnlyList Statuses { get; set; } + public IReadOnlyList Timeline { get; set; } } } diff --git a/src/GitHub.Exports/Models/PullRequestListItemModel.cs b/src/GitHub.Exports/Models/PullRequestListItemModel.cs index f6a9a7fe55..fa3a71fb9a 100644 --- a/src/GitHub.Exports/Models/PullRequestListItemModel.cs +++ b/src/GitHub.Exports/Models/PullRequestListItemModel.cs @@ -35,12 +35,27 @@ public class PullRequestListItemModel /// /// Gets or sets the pull request state (open, closed, merged). /// - public PullRequestStateEnum State { get; set; } + public PullRequestState State { get; set; } /// /// Gets the pull request checks and statuses summary /// - public PullRequestChecksState Checks { get; set; } + public PullRequestChecksSummaryState ChecksSummary { get; set; } + + /// + /// Gets the number of pending checks and statuses + /// + public int ChecksPendingCount { get; set; } + + /// + /// Gets the number of successful checks and statuses + /// + public int ChecksSuccessCount { get; set; } + + /// + /// Gets the number of erroneous checks and statuses + /// + public int ChecksErrorCount { get; set; } /// /// Gets or sets the date/time at which the pull request was last updated. diff --git a/src/GitHub.Exports/Models/PullRequestReviewCommentModel.cs b/src/GitHub.Exports/Models/PullRequestReviewCommentModel.cs index ef224242e1..535429d3c5 100644 --- a/src/GitHub.Exports/Models/PullRequestReviewCommentModel.cs +++ b/src/GitHub.Exports/Models/PullRequestReviewCommentModel.cs @@ -7,6 +7,16 @@ namespace GitHub.Models /// public class PullRequestReviewCommentModel : CommentModel { + /// + /// Gets the PullRequestId of the comment. + /// + /// + /// The GraphQL Api does not allow for deleting of pull request comments. + /// REST Api must be used, and PullRequestId is needed to reload the pull request. + /// This field should be removed with better GraphQL support. + /// + public int PullRequestId { get; set; } + /// /// Gets or sets the associated thread that contains the comment. /// diff --git a/src/GitHub.Exports/Models/PullRequestReviewThreadModel.cs b/src/GitHub.Exports/Models/PullRequestReviewThreadModel.cs index 76e05823c5..f0eb9a2d43 100644 --- a/src/GitHub.Exports/Models/PullRequestReviewThreadModel.cs +++ b/src/GitHub.Exports/Models/PullRequestReviewThreadModel.cs @@ -33,6 +33,11 @@ public class PullRequestReviewThreadModel /// public bool IsOutdated { get; set; } + /// + /// Gets or sets a value indicating whether the thread is resolved. + /// + public bool IsResolved { get; set; } + /// /// Gets or sets the line position in the diff that the thread starts on. /// diff --git a/src/GitHub.Exports/Models/UsageModel.cs b/src/GitHub.Exports/Models/UsageModel.cs index df3f2d8bef..751b443c5f 100644 --- a/src/GitHub.Exports/Models/UsageModel.cs +++ b/src/GitHub.Exports/Models/UsageModel.cs @@ -1,5 +1,8 @@ using System; +#pragma warning disable CA1034 // Nested types should not be visible +#pragma warning disable CA1720 // Identifier contains type name + namespace GitHub.Models { public class UsageModel @@ -75,6 +78,7 @@ public class MeasuresModel public int NumberOfShowCurrentPullRequest { get; set; } public int NumberOfStatusBarOpenPullRequestList { get; set; } public int NumberOfTeamExplorerHomeOpenPullRequestList { get; set; } + public int NumberOfPullRequestOpenAnnotationsList { get; set; } public int NumberOfStartPageClones { get; set; } public int NumberOfGitHubConnectSectionClones { get; set; } public int NumberOfShowRepoForkDialogClicks { get; set; } @@ -86,14 +90,12 @@ public class MeasuresModel public int ExecuteToggleInlineCommentMarginCommand { get; set; } public int NumberOfPullRequestFileMarginToggleInlineCommentMargin { get; set; } public int NumberOfPullRequestFileMarginViewChanges { get; set; } - public int NumberOfCloneViewGitHubTab { get; set; } - public int NumberOfCloneViewEnterpriseTab { get; set; } - public int NumberOfCloneViewUrlTab { get; set; } public int NumberOfGitHubClones { get; set; } public int NumberOfEnterpriseClones { get; set; } public int NumberOfGitHubOpens { get; set; } public int NumberOfEnterpriseOpens { get; set; } public int NumberOfClonesToDefaultClonePath { get; set; } + public int NumberOfPRConversationsOpened { get; set; } } } } diff --git a/src/GitHub.Exports/Models/ViewerRepositoriesModel.cs b/src/GitHub.Exports/Models/ViewerRepositoriesModel.cs index 27cba30bd1..7987af54d1 100644 --- a/src/GitHub.Exports/Models/ViewerRepositoriesModel.cs +++ b/src/GitHub.Exports/Models/ViewerRepositoriesModel.cs @@ -7,6 +7,7 @@ public class ViewerRepositoriesModel { public string Owner { get; set; } public IReadOnlyList Repositories { get; set; } - public IDictionary> OrganizationRepositories { get; set; } + public IReadOnlyList ContributedToRepositories { get; set; } + public IDictionary> Organizations { get; set; } } } diff --git a/src/GitHub.Exports/Primitives/HostAddress.cs b/src/GitHub.Exports/Primitives/HostAddress.cs index bcea51d364..e44c297716 100644 --- a/src/GitHub.Exports/Primitives/HostAddress.cs +++ b/src/GitHub.Exports/Primitives/HostAddress.cs @@ -6,7 +6,7 @@ namespace GitHub.Primitives { public class HostAddress { - public static HostAddress GitHubDotComHostAddress = new HostAddress(); + public static readonly HostAddress GitHubDotComHostAddress = new HostAddress(); static readonly Uri gistUri = new Uri("https://gist.github.com"); /// diff --git a/src/GitHub.Exports/Primitives/Paths.cs b/src/GitHub.Exports/Primitives/Paths.cs new file mode 100644 index 0000000000..9cdd4e0677 --- /dev/null +++ b/src/GitHub.Exports/Primitives/Paths.cs @@ -0,0 +1,33 @@ +using System; +using System.IO; + +namespace GitHub.Primitives +{ + /// + /// Convert to and from Git paths. + /// + public static class Paths + { + public const char GitDirectorySeparatorChar = '/'; + + /// + /// Convert from a relative path to a Git path. + /// + /// A relative path. + /// A working directory relative path which uses the '/' directory separator. + public static string ToGitPath(string relativePath) + { + return relativePath.Replace(Path.DirectorySeparatorChar, GitDirectorySeparatorChar); + } + + /// + /// Convert from a Git path to a path that uses the Windows directory separator ('\'). + /// + /// A relative path that uses the '/' directory separator. + /// A relative path that uses the directory separator ('\' on Windows). + public static string ToWindowsPath(string gitPath) + { + return gitPath.Replace(GitDirectorySeparatorChar, Path.DirectorySeparatorChar); + } + } +} diff --git a/src/GitHub.Exports/Primitives/StringEquivalent.cs b/src/GitHub.Exports/Primitives/StringEquivalent.cs index 207b84657b..e4449296ce 100644 --- a/src/GitHub.Exports/Primitives/StringEquivalent.cs +++ b/src/GitHub.Exports/Primitives/StringEquivalent.cs @@ -10,7 +10,7 @@ namespace GitHub.Primitives [Serializable] public abstract class StringEquivalent : ISerializable, IXmlSerializable where T : StringEquivalent { - protected string Value; + protected string Value { get; set; } protected StringEquivalent(string value) { diff --git a/src/GitHub.Exports/Services/GitService.cs b/src/GitHub.Exports/Services/GitService.cs index 310863f0c9..2ae7d650c5 100644 --- a/src/GitHub.Exports/Services/GitService.cs +++ b/src/GitHub.Exports/Services/GitService.cs @@ -16,11 +16,13 @@ namespace GitHub.Services public class GitService : IGitService { readonly IRepositoryFacade repositoryFacade; + readonly CompareOptions defaultCompareOptions; [ImportingConstructor] public GitService(IRepositoryFacade repositoryFacade) { this.repositoryFacade = repositoryFacade; + defaultCompareOptions = new CompareOptions { IndentHeuristic = true }; } /// @@ -38,18 +40,29 @@ public LocalRepositoryModel CreateLocalRepositoryModel(string localPath) throw new ArgumentException("Path does not exist", nameof(localPath)); } - var cloneUrl = GetUri(localPath); - var name = cloneUrl?.RepositoryName ?? dir.Name; - - var model = new LocalRepositoryModel + using (var repository = GetRepository(localPath)) { - LocalPath = localPath, - CloneUrl = cloneUrl, - Name = name, - Icon = Octicon.repo - }; + UriString cloneUrl = null; + bool noOrigin = false; + if (repository != null) + { + cloneUrl = GetUri(repository); + noOrigin = HasRemotesButNoOrigin(repository); + } + + var name = cloneUrl?.RepositoryName ?? dir.Name; - return model; + var model = new LocalRepositoryModel + { + LocalPath = localPath, + CloneUrl = cloneUrl, + HasRemotesButNoOrigin = noOrigin, + Name = name, + Icon = Octicon.repo + }; + + return model; + } } public BranchModel GetBranch(LocalRepositoryModel model) @@ -68,7 +81,8 @@ public BranchModel GetBranch(LocalRepositoryModel model) repo: model, sha: branch.Tip?.Sha, isTracking: branch.IsTracking, - trackedSha: branch.TrackedBranch?.Tip?.Sha); + trackedSha: branch.TrackedBranch?.Tip?.Sha, + trackedRemoteName: branch.TrackedBranch?.RemoteName); } } @@ -119,6 +133,17 @@ public IRepository GetRepository(string path) return repoPath == null ? null : repositoryFacade.NewRepository(repoPath); } + /// + /// Find out if repository has remotes but none are called "origin". + /// + /// The target repository. + /// True if repository has remotes but none are called "origin". + public bool HasRemotesButNoOrigin(IRepository repo) + { + var remotes = repo.Network.Remotes; + return remotes["origin"] == null && remotes.Any(); + } + /// /// Returns a representing the uri of a remote /// @@ -204,5 +229,98 @@ public Task GetLatestPushedSha(string path, string remote = "origin") } }); } + + public Task Compare( + IRepository repository, + string sha1, + string sha2, + string relativePath) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(sha1, nameof(sha1)); + Guard.ArgumentNotEmptyString(sha2, nameof(sha2)); + Guard.ArgumentIsRelativePath(relativePath, nameof(relativePath)); + + var gitPath = Paths.ToGitPath(relativePath); + return Task.Run(() => + { + var commit1 = repository.Lookup(sha1); + var commit2 = repository.Lookup(sha2); + + if (commit1 != null && commit2 != null) + { + return repository.Diff.Compare( + commit1.Tree, + commit2.Tree, + new[] { gitPath }, + defaultCompareOptions); + } + else + { + return null; + } + }); + } + + public Task CompareWith(IRepository repository, string sha1, string sha2, string relativePath, byte[] contents) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(sha1, nameof(sha1)); + Guard.ArgumentNotEmptyString(sha2, nameof(sha1)); + Guard.ArgumentIsRelativePath(relativePath, nameof(relativePath)); + + var gitPath = Paths.ToGitPath(relativePath); + return Task.Run(() => + { + var commit1 = repository.Lookup(sha1); + var commit2 = repository.Lookup(sha2); + + var treeChanges = repository.Diff.Compare(commit1.Tree, commit2.Tree, defaultCompareOptions); + var change = treeChanges.FirstOrDefault(x => x.Path == gitPath); + var oldPath = change?.OldPath; + + if (commit1 != null && oldPath != null) + { + var contentStream = contents != null ? new MemoryStream(contents) : new MemoryStream(); + var blob1 = commit1[oldPath]?.Target as Blob ?? repository.ObjectDatabase.CreateBlob(new MemoryStream()); + var blob2 = repository.ObjectDatabase.CreateBlob(contentStream, gitPath); + return repository.Diff.Compare(blob1, blob2, defaultCompareOptions); + } + + return null; + }); + } + + public Task Compare( + IRepository repository, + string sha1, + string sha2, + bool detectRenames) + { + Guard.ArgumentNotNull(repository, nameof(repository)); + Guard.ArgumentNotEmptyString(sha1, nameof(sha1)); + Guard.ArgumentNotEmptyString(sha2, nameof(sha2)); + + return Task.Run(() => + { + var options = new CompareOptions + { + Similarity = detectRenames ? SimilarityOptions.Renames : SimilarityOptions.None, + IndentHeuristic = defaultCompareOptions.IndentHeuristic + }; + + var commit1 = repository.Lookup(sha1); + var commit2 = repository.Lookup(sha2); + + if (commit1 != null && commit2 != null) + { + return repository.Diff.Compare(commit1.Tree, commit2.Tree, options); + } + else + { + return null; + } + }); + } } } \ No newline at end of file diff --git a/src/GitHub.Exports/Services/IDialogService.cs b/src/GitHub.Exports/Services/IDialogService.cs index ea26dd71e1..cc837dc5b4 100644 --- a/src/GitHub.Exports/Services/IDialogService.cs +++ b/src/GitHub.Exports/Services/IDialogService.cs @@ -25,20 +25,6 @@ public interface IDialogService /// Task ShowCloneDialog(IConnection connection, string url = null); - /// - /// Shows the re-clone dialog. - /// - /// The repository to clone. - /// - /// A task that returns the base path for the clone on success, or null if the dialog was - /// cancelled. - /// - /// - /// The re-clone dialog is shown from the VS2017+ start page when the user wants to check - /// out a repository that was previously checked out on another machine. - /// - Task ShowReCloneDialog(RepositoryModel repository); - /// /// Shows the Create Gist dialog. /// diff --git a/src/GitHub.Exports/Services/IGitHubContextService.cs b/src/GitHub.Exports/Services/IGitHubContextService.cs index 8d8906233c..3f0536fe44 100644 --- a/src/GitHub.Exports/Services/IGitHubContextService.cs +++ b/src/GitHub.Exports/Services/IGitHubContextService.cs @@ -8,6 +8,13 @@ namespace GitHub.Services /// public interface IGitHubContextService { + /// + /// Attempt to navigate to the equivalent context inside Visual Studio. + /// + /// The target repository + /// The context to open. + void TryNavigateToContext(string repositoryDir, GitHubContext context); + /// /// Find the context from a URL in the clipboard if any. /// diff --git a/src/GitHub.Exports/Services/IGitHubServiceProvider.cs b/src/GitHub.Exports/Services/IGitHubServiceProvider.cs index c1d142ed7f..0d71ce8588 100644 --- a/src/GitHub.Exports/Services/IGitHubServiceProvider.cs +++ b/src/GitHub.Exports/Services/IGitHubServiceProvider.cs @@ -12,11 +12,11 @@ public interface IGitHubServiceProvider : IServiceProvider IServiceProvider GitServiceProvider { get; set; } T GetService() where T : class; - Ret GetService() where T : class - where Ret : class; + TRet GetService() where T : class + where TRet : class; object TryGetService(Type t); - object TryGetService(string typename); + object TryGetService(string typeName); T TryGetService() where T : class; void AddService(Type t, object owner, object instance); diff --git a/src/GitHub.Exports/Services/IGitHubToolWindowManager.cs b/src/GitHub.Exports/Services/IGitHubToolWindowManager.cs new file mode 100644 index 0000000000..e7ee14312d --- /dev/null +++ b/src/GitHub.Exports/Services/IGitHubToolWindowManager.cs @@ -0,0 +1,39 @@ +using System; +using System.Threading.Tasks; +using System.Runtime.InteropServices; +using GitHub.ViewModels.GitHubPane; +using GitHub.ViewModels.Documents; +using GitHub.Primitives; + +namespace GitHub.Services +{ + /// + /// The Visual Studio service interface for accessing the GitHub Pane. + /// + [Guid("FC9EC5B5-C297-4548-A229-F8E16365543C")] + [ComVisible(true)] + public interface IGitHubToolWindowManager + { + /// + /// Ensure that the GitHub pane is created and visible. + /// + /// The view model for the GitHub Pane. + Task ShowGitHubPane(); + + /// + /// Shows a document-like tool window pane for an issue or pull request. + /// + /// + /// The host address of the server that hosts the issue or pull request. + /// + /// The repository owner. + /// The repository name. + /// The issue or pull request number. + /// The view model for the document pane. + Task ShowIssueishDocumentPane( + HostAddress address, + string owner, + string repository, + int number); + } +} diff --git a/src/GitHub.Exports/Services/IGitService.cs b/src/GitHub.Exports/Services/IGitService.cs index 4ff850c21b..1f183ced17 100644 --- a/src/GitHub.Exports/Services/IGitService.cs +++ b/src/GitHub.Exports/Services/IGitService.cs @@ -71,5 +71,44 @@ public interface IGitService /// The remote name to look for /// Task GetLatestPushedSha(string path, string remote = "origin"); + + /// + /// Compares a file in two commits. + /// + /// The repository + /// The SHA of the first commit. + /// The SHA of the second commit. + /// The relative path to the file. + /// + /// A object or null if one of the commits could not be found in the repository. + /// + Task Compare(IRepository repository, string sha1, string sha2, string path); + + /// + /// Compares a file in a commit to a string. + /// + /// The repository + /// The SHA of the first commit. + /// The SHA of the second commit. + /// The relative path to the file. + /// The contents to compare with the file. + /// + /// A object or null if the commit could not be found in the repository. + /// + /// If contains a '\'. + Task CompareWith(IRepository repository, string sha1, string sha2, string relativePath, byte[] contents); + + /// + /// Compares two commits. + /// + /// The repository + /// The SHA of the first commit. + /// The SHA of the second commit. + /// Whether to detect renames + /// + /// A object or null if one of the commits could not be found in the repository, + /// (e.g. it is from a fork). + /// + Task Compare(IRepository repository, string sha1, string sha2, bool detectRenames = false); } } \ No newline at end of file diff --git a/src/GitHub.Exports/Services/INotificationDispatcher.cs b/src/GitHub.Exports/Services/INotificationDispatcher.cs index 667219d8ea..03d6d14891 100644 --- a/src/GitHub.Exports/Services/INotificationDispatcher.cs +++ b/src/GitHub.Exports/Services/INotificationDispatcher.cs @@ -1,8 +1,10 @@ using System; using System.Windows.Input; +using System.Diagnostics.CodeAnalysis; namespace GitHub.Services { + [SuppressMessage("Microsoft.Performance", "CA1815:OverrideEqualsAndOperatorEqualsOnValueTypes")] public struct Notification { public enum NotificationType diff --git a/src/GitHub.Exports/Services/INotificationService.cs b/src/GitHub.Exports/Services/INotificationService.cs index 4e406a0ea3..b0591b7a2a 100644 --- a/src/GitHub.Exports/Services/INotificationService.cs +++ b/src/GitHub.Exports/Services/INotificationService.cs @@ -1,5 +1,6 @@ using System; using System.Windows.Input; +#pragma warning disable CA1720 // Identifier contains type name namespace GitHub.Services { diff --git a/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs b/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs index 16316aceea..808c7d0eae 100644 --- a/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs +++ b/src/GitHub.Exports/Services/ITeamExplorerServiceHolder.cs @@ -36,7 +36,7 @@ public interface ITeamExplorerServiceHolder /// /// A service for avoiding deadlocks and marshaling tasks onto the UI thread. /// - JoinableTaskFactory JoinableTaskFactory { get; } + JoinableTaskContext JoinableTaskContext { get; } IGitAwareItem HomeSection { get; } } diff --git a/src/GitHub.Exports/Services/ITeamExplorerServices.cs b/src/GitHub.Exports/Services/ITeamExplorerServices.cs index 45c204f36b..f25454c220 100644 --- a/src/GitHub.Exports/Services/ITeamExplorerServices.cs +++ b/src/GitHub.Exports/Services/ITeamExplorerServices.cs @@ -1,12 +1,14 @@ -using System.Windows.Input; +using System.Threading.Tasks; namespace GitHub.Services { public interface ITeamExplorerServices : INotificationService { void ShowConnectPage(); + void ShowCommitDetails(string oid); void ShowHomePage(); void ShowPublishSection(); + Task ShowRepositorySettingsRemotesAsync(); void ClearNotifications(); void OpenRepository(string repositoryPath); } diff --git a/src/GitHub.Exports/Services/ITippingService.cs b/src/GitHub.Exports/Services/ITippingService.cs new file mode 100644 index 0000000000..a125e8ccab --- /dev/null +++ b/src/GitHub.Exports/Services/ITippingService.cs @@ -0,0 +1,28 @@ +using System; +using System.Windows; + +namespace GitHub.Services +{ + /// + /// This service is a thin wrapper around . + /// + /// + /// The interface is public, but contained within the 'Microsoft.VisualStudio.Shell.UI.Internal' assembly. + /// To avoid a direct dependency on 'Microsoft.VisualStudio.Shell.UI.Internal', we use reflection to call this service. + /// + public interface ITippingService + { + /// + /// Show a call-out notification with the option to execute a command. + /// + /// A unique id for the callout so that is can be permanently dismissed. + /// A clickable title for the callout. + /// A plain text message for that callout that will automatically wrap. + /// True for an option to never show again. + /// A UI element for the callout to appear above which must be visible. + /// The group of the command to execute when title is clicked. + /// The ID of the command to execute when title is clicked. + void RequestCalloutDisplay(Guid calloutId, string title, string message, + bool isPermanentlyDismissible, FrameworkElement targetElement, Guid vsCommandGroupId, uint vsCommandId); + } +} diff --git a/src/GitHub.Exports/Services/IVSGitServices.cs b/src/GitHub.Exports/Services/IVSGitServices.cs index 8343ac92b2..a2b0b5fd10 100644 --- a/src/GitHub.Exports/Services/IVSGitServices.cs +++ b/src/GitHub.Exports/Services/IVSGitServices.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; using GitHub.Models; @@ -20,13 +21,15 @@ public interface IVSGitServices /// System.IProgress<Microsoft.VisualStudio.Shell.ServiceProgressData>, but /// as that type is only available in VS2017+ it is typed as here. /// + /// A cancellation token. /// /// Task Clone( string cloneUrl, string clonePath, bool recurseSubmodules, - object progress = null); + object progress = null, + CancellationToken? cancellationToken = null); string GetActiveRepoPath(); LibGit2Sharp.IRepository GetActiveRepo(); diff --git a/src/GitHub.Exports/Settings/Guids.cs b/src/GitHub.Exports/Settings/Guids.cs index dd2805aaea..0c64b37fef 100644 --- a/src/GitHub.Exports/Settings/Guids.cs +++ b/src/GitHub.Exports/Settings/Guids.cs @@ -1,5 +1,7 @@ using System; +#pragma warning disable CA1707 // Identifiers should not contain underscores + namespace GitHub.VisualStudio { public static class Guids @@ -22,7 +24,7 @@ public static class Guids public const string TeamExplorerInstall3rdPartyGitTools = "DF785C7C-8454-4836-9686-D1C4A01D0BB9"; // UIContexts - public const string UIContext_Git = "565515AD-F4C1-4D59-BC14-AE77396DDDD7"; + public const string GitContextPkgString = "565515AD-F4C1-4D59-BC14-AE77396DDDD7"; // Guids defined in GitHub.VisualStudio.vsct public const string guidGitHubPkgString = "c3d3dc68-c977-411f-b3e8-03b0dccf7dfc"; @@ -39,5 +41,8 @@ public static class Guids // Guids defined in InlineReviewsPackage.vsct public const string CommandSetString = "C5F1193E-F300-41B3-B4C4-5A703DD3C1C6"; public static readonly Guid CommandSetGuid = new Guid(CommandSetString); + + // Callout notification IDs + public static readonly Guid NoRemoteOriginCalloutId = new Guid("B5679412-58A1-49CD-96E9-8F093FE3DC79"); } } diff --git a/src/GitHub.Exports/Settings/PkgCmdID.cs b/src/GitHub.Exports/Settings/PkgCmdID.cs index 636905c377..64160c738e 100644 --- a/src/GitHub.Exports/Settings/PkgCmdID.cs +++ b/src/GitHub.Exports/Settings/PkgCmdID.cs @@ -13,6 +13,7 @@ public static class PkgCmdIDList public const int syncSubmodulesCommand = 0x203; public const int openFromUrlCommand = 0x204; public const int openFromClipboardCommand = 0x205; + public const int showIssueishDocumentCommand = 0x206; public const int backCommand = 0x300; public const int forwardCommand = 0x301; diff --git a/src/GitHub.Exports/Settings/generated/IPackageSettings.cs b/src/GitHub.Exports/Settings/generated/IPackageSettings.cs index fa8d50b008..81e7309778 100644 --- a/src/GitHub.Exports/Settings/generated/IPackageSettings.cs +++ b/src/GitHub.Exports/Settings/generated/IPackageSettings.cs @@ -1,4 +1,4 @@ -// This is an automatically generated file, based on settings.json and PackageSettingsGen.tt +// This is an automatically generated file, based on settings.json and PackageSettingsGen.tt /* settings.json content: { "settings": [ @@ -7,16 +7,6 @@ "type": "bool", "default": 'true' }, - { - "name": "EditorComments", - "type": "bool", - "default": "false" - }, - { - "name": "ForkButton", - "type": "bool", - "default": "false" - }, { "name": "UIState", "type": "object", @@ -45,10 +35,8 @@ public interface IPackageSettings : INotifyPropertyChanged { void Save(); bool CollectMetrics { get; set; } - bool EditorComments { get; set; } - bool ForkButton { get; set; } UIState UIState { get; set; } bool HideTeamExplorerWelcomeMessage { get; set; } bool EnableTraceLogging { get; set; } } -} +} \ No newline at end of file diff --git a/src/GitHub.Exports/UI/Octicon.cs b/src/GitHub.Exports/UI/Octicon.cs index 0064141067..1084c3f881 100644 --- a/src/GitHub.Exports/UI/Octicon.cs +++ b/src/GitHub.Exports/UI/Octicon.cs @@ -1,5 +1,7 @@ using System; +#pragma warning disable CA1707 // Identifiers should not contain underscores + namespace GitHub.UI { public enum Octicon diff --git a/src/GitHub.Exports/ViewModels/Documents/IIssueishPaneViewModel.cs b/src/GitHub.Exports/ViewModels/Documents/IIssueishPaneViewModel.cs new file mode 100644 index 0000000000..60d6a6ddea --- /dev/null +++ b/src/GitHub.Exports/ViewModels/Documents/IIssueishPaneViewModel.cs @@ -0,0 +1,37 @@ +using System; +using System.Threading.Tasks; +using GitHub.Models; + +namespace GitHub.ViewModels.Documents +{ + /// + /// View model for an issue or pull request document pane. + /// + public interface IIssueishPaneViewModel : IPaneViewModel + { + /// + /// Gets the content to display in the document pane. + /// + IViewModel Content { get; } + + /// + /// Gets a value indicating whether + /// has been called on the view model. + /// + bool IsInitialized { get; } + + /// + /// Loads an issue or pull request into the view model. + /// + /// The connection to use. + /// The repository owner. + /// The repository name. + /// The issue or pull request number. + /// A task that will complete when the load has finished. + Task Load( + IConnection connection, + string owner, + string name, + int number); + } +} diff --git a/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs b/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs index ecab58b39d..69eb5c1c2f 100644 --- a/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs +++ b/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubPaneViewModel.cs @@ -91,6 +91,15 @@ public interface IGitHubPaneViewModel : IViewModel /// The pull rqeuest number. Task ShowPullRequest(string owner, string repo, int number); + /// + /// Shows the details for a pull request's check run in the GitHub pane. + /// + /// The repository owner. + /// The repository name. + /// The pull rqeuest number. + /// The check run id. + Task ShowPullRequestCheckRun(string owner, string repo, int number, string checkRunId); + /// /// Shows the pull requests reviews authored by a user. /// diff --git a/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubToolWindowManager.cs b/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubToolWindowManager.cs deleted file mode 100644 index cd1c71701b..0000000000 --- a/src/GitHub.Exports/ViewModels/GitHubPane/IGitHubToolWindowManager.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Threading.Tasks; -using System.Runtime.InteropServices; - -namespace GitHub.ViewModels.GitHubPane -{ - /// - /// The Visual Studio service interface for accessing the GitHub Pane. - /// - [Guid("FC9EC5B5-C297-4548-A229-F8E16365543C")] - [ComVisible(true)] - public interface IGitHubToolWindowManager - { - /// - /// Ensure that the GitHub pane is created and visible. - /// - /// The view model for the GitHub Pane. - Task ShowGitHubPane(); - } -} diff --git a/src/GitHub.Exports/ViewModels/IPaneViewModel.cs b/src/GitHub.Exports/ViewModels/IPaneViewModel.cs new file mode 100644 index 0000000000..142ada1f77 --- /dev/null +++ b/src/GitHub.Exports/ViewModels/IPaneViewModel.cs @@ -0,0 +1,24 @@ +using System; +using System.Threading.Tasks; + +namespace GitHub.ViewModels +{ + /// + /// Represents the top-level content in a Visual Studio ToolWindowPane. + /// + public interface IPaneViewModel : IViewModel + { + /// + /// Gets the caption for the tool window. + /// + string PaneCaption { get; } + + /// + /// Initializes the view model. + /// + /// + /// The service provider for the containing ToolWindowPane. + /// + Task InitializeAsync(IServiceProvider paneServiceProvider); + } +} \ No newline at end of file diff --git a/src/GitHub.Exports/ViewModels/ISpinnerViewModel.cs b/src/GitHub.Exports/ViewModels/ISpinnerViewModel.cs new file mode 100644 index 0000000000..2478ed572b --- /dev/null +++ b/src/GitHub.Exports/ViewModels/ISpinnerViewModel.cs @@ -0,0 +1,11 @@ +using System; + +namespace GitHub.ViewModels +{ + /// + /// View model which displays a spinner. + /// + public interface ISpinnerViewModel : IViewModel + { + } +} diff --git a/src/GitHub.Extensions.Reactive/RxuiLegacy/Errors.cs b/src/GitHub.Extensions.Reactive/RxuiLegacy/Errors.cs index 18aaf66236..17e8ed6b81 100644 --- a/src/GitHub.Extensions.Reactive/RxuiLegacy/Errors.cs +++ b/src/GitHub.Extensions.Reactive/RxuiLegacy/Errors.cs @@ -337,11 +337,13 @@ public static IDisposable OverrideHandlersForTesting(Func /// This Exception will be thrown when a UserError is not handled by any /// of the registered handlers. /// - public class UnhandledUserErrorException : Exception + public class UnhandledUserErrorException : Exception { public UnhandledUserErrorException(UserError error) : base(error.ErrorMessage, error.InnerException) { @@ -350,6 +352,8 @@ public UnhandledUserErrorException(UserError error) : base(error.ErrorMessage, e public UserError ReportedError { get; protected set; } } +#pragma warning restore CA2237 // Mark ISerializable types with serializable +#pragma warning restore CA1032 // Implement standard exception constructors #pragma warning restore 618 /// diff --git a/src/GitHub.Extensions.Reactive/RxuiLegacy/ReactiveCommand.cs b/src/GitHub.Extensions.Reactive/RxuiLegacy/ReactiveCommand.cs index 5e2cff5a69..b04585a970 100644 --- a/src/GitHub.Extensions.Reactive/RxuiLegacy/ReactiveCommand.cs +++ b/src/GitHub.Extensions.Reactive/RxuiLegacy/ReactiveCommand.cs @@ -16,6 +16,11 @@ using System.Windows.Input; using Splat; +#pragma warning disable CA1030 // Use events where appropriate +#pragma warning disable CA1063 // Implement IDisposable Correctly +#pragma warning disable CA1816 // Dispose methods should call SuppressFinalize +#pragma warning disable CA2214 // Do not call overridable methods in constructors + namespace ReactiveUI.Legacy { [Obsolete("This type is obsolete and will be removed in a future version of ReactiveUI. Please switch to using ReactiveUI.ReactiveCommand instead.")] diff --git a/src/GitHub.Extensions/ExceptionExtensions.cs b/src/GitHub.Extensions/ExceptionExtensions.cs index 14df36a2c2..ee8c1d0a6b 100644 --- a/src/GitHub.Extensions/ExceptionExtensions.cs +++ b/src/GitHub.Extensions/ExceptionExtensions.cs @@ -14,7 +14,7 @@ public static bool IsCriticalException(this Exception exception) { if (exception == null) { - throw new ArgumentNullException("exception"); + throw new ArgumentNullException(nameof(exception)); } return exception.IsFatalException() @@ -36,7 +36,7 @@ public static bool IsFatalException(this Exception exception) { if (exception == null) { - throw new ArgumentNullException("exception"); + throw new ArgumentNullException(nameof(exception)); } return exception is StackOverflowException diff --git a/src/GitHub.Extensions/GitHub.Extensions.csproj b/src/GitHub.Extensions/GitHub.Extensions.csproj index 0c0995eb37..8548343196 100644 --- a/src/GitHub.Extensions/GitHub.Extensions.csproj +++ b/src/GitHub.Extensions/GitHub.Extensions.csproj @@ -26,7 +26,5 @@ - - diff --git a/src/GitHub.Extensions/GlobalSuppressions.cs b/src/GitHub.Extensions/GlobalSuppressions.cs new file mode 100644 index 0000000000..1747e54f03 --- /dev/null +++ b/src/GitHub.Extensions/GlobalSuppressions.cs @@ -0,0 +1,10 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Design", "CA1054:Uri parameters should not be strings")] +[assembly: SuppressMessage("Design", "CA1056:Uri properties should not be strings")] +[assembly: SuppressMessage("Reliability", "CA2007:Do not directly await a Task", Justification = "Discouraged for VSSDK projects.")] diff --git a/src/GitHub.Extensions/Guard.cs b/src/GitHub.Extensions/Guard.cs index 240dbd4cbd..82bf95c509 100644 --- a/src/GitHub.Extensions/Guard.cs +++ b/src/GitHub.Extensions/Guard.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Generic; -using System.Diagnostics; +using System.IO; using System.Globalization; using System.Linq; @@ -8,6 +7,16 @@ namespace GitHub.Extensions { public static class Guard { + public static void ArgumentIsRelativePath(string value, string name) + { + ArgumentNotNull(value, name); + + if (Path.IsPathRooted(value)) + { + throw new ArgumentException($"The value '{value}' must not be rooted", name); + } + } + public static void ArgumentNotNull(object value, string name) { if (value != null) return; diff --git a/src/GitHub.Extensions/ReflectionExtensions.cs b/src/GitHub.Extensions/ReflectionExtensions.cs index e69c65f1d5..df10dba2f4 100644 --- a/src/GitHub.Extensions/ReflectionExtensions.cs +++ b/src/GitHub.Extensions/ReflectionExtensions.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; namespace GitHub.Extensions { @@ -53,5 +54,20 @@ public static string GetCustomAttributeValue(this Assembly assembly, string p var value = propertyInfo.GetValue(attribute, null); return value.ToString(); } + + public static T CreateUninitialized() + { + // WARNING: THIS METHOD IS PURE EVIL! + // Only use this in cases where T is sealed and has an internal ctor and + // you're SURE the API you're passing it into won't do anything interesting with it. + // Even then, consider refactoring. + return (T)FormatterServices.GetUninitializedObject(typeof(T)); + } + + public static void Invoke(object obj, string methodName, params object[] parameters) + { + var method = obj.GetType().GetMethod(methodName, BindingFlags.Instance | BindingFlags.NonPublic); + method.Invoke(obj, parameters); + } } } diff --git a/src/GitHub.Extensions/StringExtensions.cs b/src/GitHub.Extensions/StringExtensions.cs index 9e3c649e25..be59463011 100644 --- a/src/GitHub.Extensions/StringExtensions.cs +++ b/src/GitHub.Extensions/StringExtensions.cs @@ -109,6 +109,26 @@ public static string EnsureEndsWith(this string s, char c) return s.TrimEnd(c) + c; } + public static string EnsureValidPath(this string path) + { + if (string.IsNullOrEmpty(path)) return null; + + var components = path.Split(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); + var result = new StringBuilder(); + + foreach (var component in components) + { + if (result.Length > 0) + { + result.Append(Path.DirectorySeparatorChar); + } + + result.Append(CoerceValidFileName(component)); + } + + return result.ToString(); + } + public static string NormalizePath(this string path) { if (String.IsNullOrEmpty(path)) return null; @@ -243,5 +263,33 @@ public static string GetSha256Hash(this string input) return string.Join("", hash.Select(b => b.ToString("x2", CultureInfo.InvariantCulture))); } } + + /// + /// Strip illegal chars and reserved words from a candidate filename (should not include the directory path) + /// + /// + /// http://stackoverflow.com/questions/309485/c-sharp-sanitize-file-name + /// + static string CoerceValidFileName(string filename) + { + var invalidChars = Regex.Escape(new string(Path.GetInvalidFileNameChars())); + var invalidReStr = string.Format(CultureInfo.InvariantCulture, @"[{0}]+", invalidChars); + + var reservedWords = new[] + { + "CON", "PRN", "AUX", "CLOCK$", "NUL", "COM0", "COM1", "COM2", "COM3", "COM4", + "COM5", "COM6", "COM7", "COM8", "COM9", "LPT0", "LPT1", "LPT2", "LPT3", "LPT4", + "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" + }; + + var sanitisedNamePart = Regex.Replace(filename, invalidReStr, "_"); + foreach (var reservedWord in reservedWords) + { + var reservedWordPattern = string.Format(CultureInfo.InvariantCulture, "^{0}\\.", reservedWord); + sanitisedNamePart = Regex.Replace(sanitisedNamePart, reservedWordPattern, "_reservedWord_.", RegexOptions.IgnoreCase); + } + + return sanitisedNamePart; + } } } diff --git a/src/GitHub.InlineReviews/Commands/InlineCommentNavigationCommand.cs b/src/GitHub.InlineReviews/Commands/InlineCommentNavigationCommand.cs index fe810fc3df..58d43b4545 100644 --- a/src/GitHub.InlineReviews/Commands/InlineCommentNavigationCommand.cs +++ b/src/GitHub.InlineReviews/Commands/InlineCommentNavigationCommand.cs @@ -9,9 +9,11 @@ using GitHub.Models; using GitHub.Services; using GitHub.Services.Vssdk.Commands; +using Microsoft; using Microsoft.VisualStudio; using Microsoft.VisualStudio.ComponentModelHost; using Microsoft.VisualStudio.Editor; +using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Differencing; @@ -79,7 +81,7 @@ protected int GetCursorPoint(ITextView textView, InlineCommentNavigationParams p /// The text view containing the buffer /// The 0-based line number. /// - protected int GetCursorPoint(ITextView textView, int lineNumber) + protected static int GetCursorPoint(ITextView textView, int lineNumber) { lineNumber = Math.Max(0, Math.Min(lineNumber, textView.TextSnapshot.LineCount - 1)); return textView.TextSnapshot.GetLineFromLineNumber(lineNumber).Start.Position; @@ -99,6 +101,8 @@ protected int GetCursorPoint(ITextView textView, int lineNumber) /// protected IEnumerable GetCurrentTextViews() { + ThreadHelper.ThrowIfNotOnUIThread(); + var result = new List(); try @@ -164,6 +168,8 @@ protected IEnumerable GetCurrentTextViews() } var model = (IComponentModel)serviceProvider.GetService(typeof(SComponentModel)); + Assumes.Present(model); + var adapterFactory = model.GetService(); var wpfTextView = adapterFactory.GetWpfTextView(textView); result.Add(wpfTextView); @@ -256,7 +262,7 @@ protected void ShowPeekComments( } } - SnapshotPoint? Map(IMappingPoint p, ITextSnapshot textSnapshot) + static SnapshotPoint? Map(IMappingPoint p, ITextSnapshot textSnapshot) { return p.GetPoint(textSnapshot.TextBuffer, PositionAffinity.Predecessor); } diff --git a/src/GitHub.InlineReviews/Commands/ToggleInlineCommentMarginCommand.cs b/src/GitHub.InlineReviews/Commands/ToggleInlineCommentMarginCommand.cs index 01f817b2ed..b01ad42f4c 100644 --- a/src/GitHub.InlineReviews/Commands/ToggleInlineCommentMarginCommand.cs +++ b/src/GitHub.InlineReviews/Commands/ToggleInlineCommentMarginCommand.cs @@ -3,6 +3,7 @@ using System.ComponentModel.Composition; using GitHub.Commands; using GitHub.Services; +using GitHub.Extensions; using GitHub.VisualStudio; using GitHub.InlineReviews.Margins; using GitHub.Services.Vssdk.Commands; @@ -42,7 +43,7 @@ public ToggleInlineCommentMarginCommand( public override Task Execute() { - usageTracker.Value.IncrementCounter(x => x.ExecuteToggleInlineCommentMarginCommand); + usageTracker.Value.IncrementCounter(x => x.ExecuteToggleInlineCommentMarginCommand).Forget(); IVsTextView activeView = null; if (textManager.Value.GetActiveView(1, null, out activeView) == VSConstants.S_OK) diff --git a/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj index ef3c381721..12ca598f0e 100644 --- a/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj +++ b/src/GitHub.InlineReviews/GitHub.InlineReviews.csproj @@ -1,7 +1,5 @@  - - $(VisualStudioVersion) @@ -30,8 +28,6 @@ true true true - ..\common\GitHubVS.ruleset - true true False False @@ -46,20 +42,12 @@ false bin\Debug\ - - false - TRACE;DEBUG;CODE_ANALYSIS - prompt - 4 - true - bin\Debug\ - true TRACE prompt 4 - true + false bin\Release\ @@ -71,9 +59,8 @@ + - - @@ -102,12 +89,14 @@ - + + ShowInlineAnnotationGlyph.xaml + + + ShowInlineCommentAnnotationGlyph.xaml + - - PullRequestFileMarginView.xaml - GlyphMarginGrid.xaml @@ -134,11 +123,14 @@ - - Designer - - - + + {3321ce72-26ed-4d1e-a8f5-6901fb783007} + Octokit.GraphQL.Core + + + {791b408c-0abc-465b-9eb1-a2422d67f418} + Octokit.GraphQL + {08dd4305-7787-4823-a53f-4d0f725a07f3} Octokit @@ -205,170 +197,13 @@ - - False - - - False - - - False - - - False - - - ..\..\packages\LibGit2Sharp.0.23.1\lib\net40\LibGit2Sharp.dll - True - - - ..\..\packages\Markdig.Signed.0.13.0\lib\net40\Markdig.dll - True - - - ..\..\packages\Markdig.Wpf.Signed.0.2.1\lib\net452\Markdig.Wpf.dll - True - False - - ..\..\packages\Microsoft.VisualStudio.ComponentModelHost.14.0.25424\lib\net45\Microsoft.VisualStudio.ComponentModelHost.dll - True - - - ..\..\packages\Microsoft.VisualStudio.CoreUtility.14.3.25407\lib\net45\Microsoft.VisualStudio.CoreUtility.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Editor.14.3.25407\lib\net45\Microsoft.VisualStudio.Editor.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Imaging.14.3.25407\lib\net45\Microsoft.VisualStudio.Imaging.dll - True - - - True - ..\..\packages\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Language.Intellisense.14.3.25407\lib\net45\Microsoft.VisualStudio.Language.Intellisense.dll - True - - - ..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.14.0.14.3.25407\lib\Microsoft.VisualStudio.Shell.14.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Immutable.12.0.12.0.21003\lib\net45\Microsoft.VisualStudio.Shell.Immutable.12.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.3.25407\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll - True - - - True - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll - True - - - True - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll - True - - - True - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll - True - - - True - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.14.3.25407\lib\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Text.Data.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Data.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Text.Logic.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.Logic.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Text.UI.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Text.UI.Wpf.14.3.25407\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll - True - - - ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll - True - - - ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Threading.14.1.111\lib\net45\Microsoft.VisualStudio.Threading.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Utilities.14.3.25407\lib\net45\Microsoft.VisualStudio.Utilities.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll - True - - - ..\..\packages\Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll - - - ..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.dll - - - ..\..\packages\Octokit.GraphQL.0.1.1-beta\lib\netstandard1.1\Octokit.GraphQL.Core.dll - - - ..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll - True - - - False - @@ -376,12 +211,6 @@ - - ..\..\packages\System.Reactive.4.0.0\lib\net46\System.Reactive.dll - - - ..\..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll - @@ -408,9 +237,13 @@ Designer true - + + MSBuild:Compile Designer + + MSBuild:Compile + Designer Designer @@ -434,31 +267,43 @@ - - - - - PreserveNewest - + - + + + 0.2.1 + + + 15.8.36 + + + 15.8.192 + + + 15.8.3252 + runtime; build; native; contentfiles; analyzers + all + + + 2.5.0 + + + 0.12.0 + + + 4.0.0 + + + 4.5.0 + + + 12.0.4 + + - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - + + + diff --git a/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs b/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs index 55eeb756e6..04ad51a9d3 100644 --- a/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs +++ b/src/GitHub.InlineReviews/PullRequestStatusBarPackage.cs @@ -12,7 +12,7 @@ namespace GitHub.InlineReviews { [Guid(Guids.PullRequestStatusPackageId)] [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] - [ProvideAutoLoad(Guids.UIContext_Git, PackageAutoLoadFlags.BackgroundLoad)] + [ProvideAutoLoad(Guids.GitContextPkgString, PackageAutoLoadFlags.BackgroundLoad)] public class PullRequestStatusBarPackage : AsyncPackage { /// diff --git a/src/GitHub.InlineReviews/Resources/logo_32x32@2x.png b/src/GitHub.InlineReviews/Resources/logo_32x32@2x.png deleted file mode 100644 index 1fd18c1c7a..0000000000 Binary files a/src/GitHub.InlineReviews/Resources/logo_32x32@2x.png and /dev/null differ diff --git a/src/GitHub.InlineReviews/Services/DiffService.cs b/src/GitHub.InlineReviews/Services/DiffService.cs index 625bf7492e..1dccd7120e 100644 --- a/src/GitHub.InlineReviews/Services/DiffService.cs +++ b/src/GitHub.InlineReviews/Services/DiffService.cs @@ -16,12 +16,12 @@ namespace GitHub.InlineReviews.Services [PartCreationPolicy(CreationPolicy.NonShared)] public class DiffService : IDiffService { - readonly IGitClient gitClient; + readonly IGitService gitService; [ImportingConstructor] - public DiffService(IGitClient gitClient) + public DiffService(IGitService gitService) { - this.gitClient = gitClient; + this.gitService = gitService; } /// @@ -29,9 +29,9 @@ public async Task> Diff( IRepository repo, string baseSha, string headSha, - string path) + string relativePath) { - var patch = await gitClient.Compare(repo, baseSha, headSha, path); + var patch = await gitService.Compare(repo, baseSha, headSha, relativePath); if (patch != null) { @@ -39,7 +39,7 @@ public async Task> Diff( } else { - return new DiffChunk[0]; + return Array.Empty(); } } @@ -51,7 +51,7 @@ public async Task> Diff( string path, byte[] contents) { - var changes = await gitClient.CompareWith(repo, baseSha, headSha, path, contents); + var changes = await gitService.CompareWith(repo, baseSha, headSha, path, contents); if (changes?.Patch != null) { @@ -59,7 +59,7 @@ public async Task> Diff( } else { - return new DiffChunk[0]; + return Array.Empty(); } } } diff --git a/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs index 7a3d9c845d..608ccf0932 100644 --- a/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs +++ b/src/GitHub.InlineReviews/Services/IPullRequestSessionService.cs @@ -44,6 +44,20 @@ Task> Diff( string relativePath, byte[] contents); + + + /// + /// Builds a set of annotation models for a file based on a pull request model + /// + /// The pull request session. + /// The relative path to the file. + /// + /// A collection of objects. + /// + IReadOnlyList BuildAnnotations( + PullRequestDetailModel pullRequest, + string relativePath); + /// /// Builds a set of comment thread models for a file based on a pull request model and a diff. /// @@ -142,8 +156,14 @@ Task ExtractFileFromGit( /// The repository owner. /// The repository name. /// The pull request number. + /// Whether the data should be refreshed instead of read from the cache. /// A task returning the pull request model. - Task ReadPullRequestDetail(HostAddress address, string owner, string name, int number); + Task ReadPullRequestDetail( + HostAddress address, + string owner, + string name, + int number, + bool refresh = false); /// /// Reads the current viewer for the specified address.. diff --git a/src/GitHub.InlineReviews/Services/PullRequestSession.cs b/src/GitHub.InlineReviews/Services/PullRequestSession.cs index 88f2995824..275780d8dd 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSession.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSession.cs @@ -81,12 +81,12 @@ public async Task GetFile( try { PullRequestSessionFile file; - var normalizedPath = relativePath.Replace("\\", "/"); - var key = normalizedPath + '@' + commitSha; + var gitPath = Paths.ToGitPath(relativePath); + var key = gitPath + '@' + commitSha; if (!fileIndex.TryGetValue(key, out file)) { - file = new PullRequestSessionFile(normalizedPath, commitSha); + file = new PullRequestSessionFile(relativePath, commitSha); await UpdateFile(file); fileIndex.Add(key, file); } @@ -110,22 +110,6 @@ public async Task GetMergeBase() return mergeBase; } - /// - public string GetRelativePath(string path) - { - if (Path.IsPathRooted(path)) - { - var basePath = LocalRepository.LocalPath; - - if (path.StartsWith(basePath, StringComparison.OrdinalIgnoreCase) && path.Length > basePath.Length + 1) - { - return path.Substring(basePath.Length + 1); - } - } - - return null; - } - /// public async Task PostReviewComment( string body, @@ -268,7 +252,8 @@ public async Task Refresh() address, RepositoryOwner, LocalRepository.Name, - PullRequest.Number); + PullRequest.Number, + true); await Update(model); } @@ -304,12 +289,12 @@ async Task AddComment(PullRequestReviewCommentModel comment) async Task UpdateFile(PullRequestSessionFile file) { - await Task.Delay(0); var mergeBaseSha = await GetMergeBase(); file.BaseSha = PullRequest.BaseRefSha; file.CommitSha = file.IsTrackingHead ? PullRequest.HeadRefSha : file.CommitSha; file.Diff = await service.Diff(LocalRepository, mergeBaseSha, file.CommitSha, file.RelativePath); file.InlineCommentThreads = service.BuildCommentThreads(PullRequest, file.RelativePath, file.Diff, file.CommitSha); + file.InlineAnnotations = service.BuildAnnotations(PullRequest, file.RelativePath); } void UpdatePendingReview() diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs index d9f31eba5e..5c5e816173 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionManager.cs @@ -19,6 +19,8 @@ using ReactiveUI; using Serilog; +#pragma warning disable CA1308 // Normalize strings to uppercase + namespace GitHub.InlineReviews.Services { /// @@ -227,6 +229,13 @@ async Task StatusChanged() async Task GetSessionInternal(string owner, string name, int number) { + var cloneUrl = repository.CloneUrl; + if (cloneUrl == null) + { + // Can't create a session from a repository with no origin + return null; + } + PullRequestSession session = null; WeakReference weakSession; var key = Tuple.Create(owner.ToLowerInvariant(), number); @@ -238,7 +247,7 @@ async Task GetSessionInternal(string owner, string name, int if (session == null) { - var address = HostAddress.Create(repository.CloneUrl); + var address = HostAddress.Create(cloneUrl); var pullRequest = await sessionService.ReadPullRequestDetail(address, owner, name, number); session = new PullRequestSession( diff --git a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs index 8f8aa08f41..7b60f9bb29 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestSessionService.cs @@ -8,7 +8,6 @@ using System.Text; using System.Threading.Tasks; using GitHub.Api; -using GitHub.App.Services; using GitHub.Factories; using GitHub.InlineReviews.Models; using GitHub.Models; @@ -18,22 +17,15 @@ using LibGit2Sharp; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Projection; -using Octokit; using Octokit.GraphQL; -using Octokit.GraphQL.Core; using Octokit.GraphQL.Model; using ReactiveUI; using Serilog; using PullRequestReviewEvent = Octokit.PullRequestReviewEvent; using static Octokit.GraphQL.Variable; -using CheckAnnotationLevel = GitHub.Models.CheckAnnotationLevel; -using CheckConclusionState = GitHub.Models.CheckConclusionState; -using CheckStatusState = GitHub.Models.CheckStatusState; using DraftPullRequestReviewComment = Octokit.GraphQL.Model.DraftPullRequestReviewComment; using FileMode = System.IO.FileMode; using NotFoundException = LibGit2Sharp.NotFoundException; -using PullRequestReviewState = Octokit.GraphQL.Model.PullRequestReviewState; -using StatusState = GitHub.Models.StatusState; // GraphQL DatabaseId field are marked as deprecated, but we need them for interop with REST. #pragma warning disable CS0618 @@ -47,7 +39,8 @@ namespace GitHub.InlineReviews.Services public class PullRequestSessionService : IPullRequestSessionService { static readonly ILogger log = LogManager.ForContext(); - static ICompiledQuery readPullRequest; + static ICompiledQuery readPullRequestWithResolved; + static ICompiledQuery readPullRequestWithoutResolved; static ICompiledQuery> readCommitStatuses; static ICompiledQuery> readCommitStatusesEnterprise; static ICompiledQuery readViewer; @@ -97,6 +90,23 @@ public virtual async Task> Diff(LocalRepositoryModel re } } + /// + public IReadOnlyList BuildAnnotations( + PullRequestDetailModel pullRequest, + string relativePath) + { + var gitPath = Paths.ToGitPath(relativePath); + + return pullRequest.CheckSuites + ?.SelectMany(checkSuite => checkSuite.CheckRuns.Select(checkRun => new { checkSuite, checkRun })) + .SelectMany(arg => + arg.checkRun.Annotations + .Where(annotation => annotation.Path == gitPath) + .Select(annotation => new InlineAnnotationModel(arg.checkSuite, arg.checkRun, annotation))) + .OrderBy(tuple => tuple.StartLine) + .ToArray(); + } + /// public IReadOnlyList BuildCommentThreads( PullRequestDetailModel pullRequest, @@ -104,17 +114,18 @@ public IReadOnlyList BuildCommentThreads( IReadOnlyList diff, string headSha) { - relativePath = relativePath.Replace("\\", "/"); + var gitPath = Paths.ToGitPath(relativePath); var threadsByPosition = pullRequest.Threads - .Where(x => x.Path == relativePath && !x.IsOutdated) + .Where(x => x.Path == gitPath) .OrderBy(x => x.Id) .GroupBy(x => Tuple.Create(x.OriginalCommitSha, x.OriginalPosition)); var threads = new List(); foreach (var thread in threadsByPosition) { - var hunk = thread.First().DiffHunk; + var reviewThread = thread.First(); + var hunk = reviewThread.DiffHunk; var chunks = DiffUtilities.ParseFragment(hunk); var chunk = chunks.Last(); var diffLines = chunk.Lines.Reverse().Take(5).ToList(); @@ -133,7 +144,8 @@ public IReadOnlyList BuildCommentThreads( { Comment = c, Review = pullRequest.Reviews.FirstOrDefault(x => x.Comments.Contains(c)), - }))); + })), + reviewThread.IsResolved); threads.Add(inlineThread); } @@ -278,13 +290,29 @@ public async Task ReadFileAsync(string path) return null; } - public virtual async Task ReadPullRequestDetail(HostAddress address, string owner, string name, int number) + public virtual Task ReadPullRequestDetail(HostAddress address, string owner, string name, int number, bool refresh = false) { - if (readPullRequest == null) + // The reviewThreads/isResolved field is only guaranteed to be available on github.com + if (address.IsGitHubDotCom()) { - readPullRequest = new Query() - .Repository(Var(nameof(owner)), Var(nameof(name))) - .PullRequest(Var(nameof(number))) + return ReadPullRequestDetailWithResolved(address, owner, name, number, refresh); + } + else + { + return ReadPullRequestDetailWithoutResolved(address, owner, name, number, refresh); + } + } + + async Task ReadPullRequestDetailWithResolved(HostAddress address, string owner, + string name, int number, bool refresh) + { + var itemTypes = new[] { PullRequestTimelineItemsItemType.IssueComment, PullRequestTimelineItemsItemType.PullRequestCommit }; + + if (readPullRequestWithResolved == null) + { + readPullRequestWithResolved = new Query() + .Repository(owner: Var(nameof(owner)), name: Var(nameof(name))) + .PullRequest(number: Var(nameof(number))) .Select(pr => new PullRequestDetailModel { Id = pr.Id.Value, @@ -304,6 +332,206 @@ public virtual async Task ReadPullRequestDetail(HostAddr HeadRepositoryOwner = pr.HeadRepositoryOwner != null ? pr.HeadRepositoryOwner.Login : null, State = pr.State.FromGraphQl(), UpdatedAt = pr.UpdatedAt, + CommentCount = pr.Comments(0, null, null, null).TotalCount, + Comments = pr.Comments(null, null, null, null).AllPages().Select(comment => new CommentModel + { + Id = comment.Id.Value, + Author = new ActorModel + { + Login = comment.Author.Login, + AvatarUrl = comment.Author.AvatarUrl(null), + }, + Body = comment.Body, + CreatedAt = comment.CreatedAt, + DatabaseId = comment.DatabaseId.Value, + Url = comment.Url, + }).ToList(), + Threads = pr.ReviewThreads(null, null, null, null).AllPages().Select(thread => new PullRequestReviewThreadModel + { + Comments = thread.Comments(null, null, null, null, null).AllPages().Select(comment => new CommentAdapter + { + Id = comment.Id.Value, + PullRequestId = comment.PullRequest.Number, + DatabaseId = comment.DatabaseId.Value, + Author = new ActorModel + { + Login = comment.Author.Login, + AvatarUrl = comment.Author.AvatarUrl(null), + }, + Body = comment.Body, + Path = comment.Path, + CommitSha = comment.Commit.Oid, + DiffHunk = comment.DiffHunk, + Position = comment.Position, + OriginalPosition = comment.OriginalPosition, + OriginalCommitId = comment.OriginalCommit.Oid, + ReplyTo = comment.ReplyTo != null ? comment.ReplyTo.Id.Value : null, + PullRequestReviewId = comment.PullRequestReview != null ? comment.PullRequestReview.Id.Value : null, + CreatedAt = comment.CreatedAt, + Url = comment.Url + }).ToList(), + IsResolved = thread.IsResolved + }).ToList(), + Reviews = pr.Reviews(null, null, null, null, null, null).AllPages().Select(review => new PullRequestReviewModel + { + Id = review.Id.Value, + Body = review.Body, + CommitId = review.Commit.Oid, + State = review.State.FromGraphQl(), + SubmittedAt = review.SubmittedAt, + Author = new ActorModel + { + Login = review.Author.Login, + AvatarUrl = review.Author.AvatarUrl(null) + } + }).ToList(), + Timeline = pr.TimelineItems(null, null, null, null, itemTypes, null, null).AllPages().Select(item => item.Switch(when => + when.PullRequestCommit(commit => new CommitModel + { + AbbreviatedOid = commit.Commit.AbbreviatedOid, + Author = new CommitActorModel + { + Name = commit.Commit.Author.Name, + Email = commit.Commit.Author.Email, + User = commit.Commit.Author.User != null ? new ActorModel + { + Login = commit.Commit.Author.User.Login, + AvatarUrl = commit.Commit.Author.User.AvatarUrl(null), + } : null + }, + MessageHeadline = commit.Commit.MessageHeadline, + Oid = commit.Commit.Oid, + }).IssueComment(comment => new CommentModel + { + Author = new ActorModel + { + Login = comment.Author.Login, + AvatarUrl = comment.Author.AvatarUrl(null), + }, + Body = comment.Body, + CreatedAt = comment.CreatedAt, + DatabaseId = comment.DatabaseId.Value, + Id = comment.Id.Value, + Url = comment.Url, + }))).ToList() + }).Compile(); + } + + var vars = new Dictionary + { + { nameof(owner), owner }, + { nameof(name), name }, + { nameof(number), number }, + }; + + var connection = await graphqlFactory.CreateConnection(address); + var result = await connection.Run(readPullRequestWithResolved, vars, refresh); + + var apiClient = await apiClientFactory.Create(address); + + var files = await log.TimeAsync(nameof(apiClient.GetPullRequestFiles), + async () => await apiClient.GetPullRequestFiles(owner, name, number).ToList()); + + var lastCommitModel = await log.TimeAsync(nameof(GetPullRequestLastCommitAdapter), + () => GetPullRequestLastCommitAdapter(address, owner, name, number, refresh)); + + result.Statuses = (IReadOnlyList)lastCommitModel.Statuses ?? Array.Empty(); + + if (lastCommitModel.CheckSuites == null) + { + result.CheckSuites = Array.Empty(); + } + else + { + result.CheckSuites = lastCommitModel.CheckSuites; + foreach (var checkSuite in result.CheckSuites) + { + checkSuite.HeadSha = lastCommitModel.HeadSha; + } + } + + result.ChangedFiles = files.Select(file => new PullRequestFileModel + { + FileName = file.FileName, + Sha = file.Sha, + Status = (PullRequestFileStatus)Enum.Parse(typeof(PullRequestFileStatus), file.Status, true), + }).ToList(); + + foreach (var thread in result.Threads) + { + if (thread.Comments.Count > 0 && thread.Comments[0] is CommentAdapter adapter) + { + thread.CommitSha = adapter.CommitSha; + thread.DiffHunk = adapter.DiffHunk; + thread.Id = adapter.Id; + thread.IsOutdated = adapter.Position == null; + thread.OriginalCommitSha = adapter.OriginalCommitId; + thread.OriginalPosition = adapter.OriginalPosition; + thread.Path = adapter.Path; + thread.Position = adapter.Position; + + foreach (var comment in thread.Comments) + { + comment.Thread = thread; + } + } + } + + foreach (var review in result.Reviews) + { + review.Comments = result.Threads + .SelectMany(t => t.Comments) + .Cast() + .Where(c => c.PullRequestReviewId == review.Id) + .ToList(); + } + + return result; + } + + async Task ReadPullRequestDetailWithoutResolved(HostAddress address, string owner, + string name, int number, bool refresh) + { + var itemTypes = new[] { PullRequestTimelineItemsItemType.IssueComment, PullRequestTimelineItemsItemType.PullRequestCommit }; + + if (readPullRequestWithoutResolved == null) + { + readPullRequestWithoutResolved = new Query() + .Repository(owner: Var(nameof(owner)), name: Var(nameof(name))) + .PullRequest(number: Var(nameof(number))) + .Select(pr => new PullRequestDetailModel + { + Id = pr.Id.Value, + Number = pr.Number, + Author = new ActorModel + { + Login = pr.Author.Login, + AvatarUrl = pr.Author.AvatarUrl(null), + }, + Title = pr.Title, + Body = pr.Body, + BaseRefSha = pr.BaseRefOid, + BaseRefName = pr.BaseRefName, + BaseRepositoryOwner = pr.Repository.Owner.Login, + HeadRefName = pr.HeadRefName, + HeadRefSha = pr.HeadRefOid, + HeadRepositoryOwner = pr.HeadRepositoryOwner != null ? pr.HeadRepositoryOwner.Login : null, + State = pr.State.FromGraphQl(), + UpdatedAt = pr.UpdatedAt, + CommentCount = pr.Comments(0, null, null, null).TotalCount, + Comments = pr.Comments(null, null, null, null).AllPages().Select(comment => new CommentModel + { + Id = comment.Id.Value, + Author = new ActorModel + { + Login = comment.Author.Login, + AvatarUrl = comment.Author.AvatarUrl(null), + }, + Body = comment.Body, + CreatedAt = comment.CreatedAt, + DatabaseId = comment.DatabaseId.Value, + Url = comment.Url, + }).ToList(), Reviews = pr.Reviews(null, null, null, null, null, null).AllPages().Select(review => new PullRequestReviewModel { Id = review.Id.Value, @@ -338,6 +566,34 @@ public virtual async Task ReadPullRequestDetail(HostAddr Url = comment.Url, }).ToList(), }).ToList(), + Timeline = pr.TimelineItems(null, null, null, null, itemTypes, null, null).AllPages().Select(item => item.Switch(when => + when.PullRequestCommit(commit => new CommitModel + { + AbbreviatedOid = commit.Commit.AbbreviatedOid, + Author = new CommitActorModel { + Name = commit.Commit.Author.Name, + Email = commit.Commit.Author.Email, + User = commit.Commit.Author.User != null ? new ActorModel + { + Login = commit.Commit.Author.User.Login, + AvatarUrl = commit.Commit.Author.User.AvatarUrl(null), + } : null + }, + MessageHeadline = commit.Commit.MessageHeadline, + Oid = commit.Commit.Oid, + }).IssueComment(comment => new CommentModel + { + Author = new ActorModel + { + Login = comment.Author.Login, + AvatarUrl = comment.Author.AvatarUrl(null), + }, + Body = comment.Body, + CreatedAt = comment.CreatedAt, + DatabaseId = comment.DatabaseId.Value, + Id = comment.Id.Value, + Url = comment.Url, + }))).ToList() }).Compile(); } @@ -349,14 +605,30 @@ public virtual async Task ReadPullRequestDetail(HostAddr }; var connection = await graphqlFactory.CreateConnection(address); - var result = await connection.Run(readPullRequest, vars); + var result = await connection.Run(readPullRequestWithoutResolved, vars, refresh); var apiClient = await apiClientFactory.Create(address); - var files = await apiClient.GetPullRequestFiles(owner, name, number).ToList(); - var lastCommitModel = await GetPullRequestLastCommitAdapter(address, owner, name, number); - result.Statuses = lastCommitModel.Statuses; - result.CheckSuites = lastCommitModel.CheckSuites; + var files = await log.TimeAsync(nameof(apiClient.GetPullRequestFiles), + async () => await apiClient.GetPullRequestFiles(owner, name, number).ToList()); + + var lastCommitModel = await log.TimeAsync(nameof(GetPullRequestLastCommitAdapter), + () => GetPullRequestLastCommitAdapter(address, owner, name, number, refresh)); + + result.Statuses = (IReadOnlyList)lastCommitModel.Statuses ?? Array.Empty(); + + if (lastCommitModel.CheckSuites == null) + { + result.CheckSuites = Array.Empty(); + } + else + { + result.CheckSuites = lastCommitModel.CheckSuites; + foreach (var checkSuite in result.CheckSuites) + { + checkSuite.HeadSha = lastCommitModel.HeadSha; + } + } result.ChangedFiles = files.Select(file => new PullRequestFileModel { @@ -365,7 +637,63 @@ public virtual async Task ReadPullRequestDetail(HostAddr Status = (PullRequestFileStatus)Enum.Parse(typeof(PullRequestFileStatus), file.Status, true), }).ToList(); - BuildPullRequestThreads(result); + // Build pull request threads + var commentsByReplyId = new Dictionary>(); + + // Get all comments that are not replies. + foreach (CommentAdapter comment in result.Reviews.SelectMany(x => x.Comments)) + { + if (comment.ReplyTo == null) + { + commentsByReplyId.Add(comment.Id, new List { comment }); + } + } + + // Get the comments that are replies and place them into the relevant list. + foreach (CommentAdapter comment in result.Reviews.SelectMany(x => x.Comments).OrderBy(x => x.CreatedAt)) + { + if (comment.ReplyTo != null) + { + List thread = null; + + if (commentsByReplyId.TryGetValue(comment.ReplyTo, out thread)) + { + thread.Add(comment); + } + } + } + + // Build a collection of threads for the information collected above. + var threads = new List(); + + foreach (var threadSource in commentsByReplyId) + { + var adapter = threadSource.Value[0]; + + var thread = new PullRequestReviewThreadModel + { + Comments = threadSource.Value, + CommitSha = adapter.CommitSha, + DiffHunk = adapter.DiffHunk, + Id = adapter.Id, + IsOutdated = adapter.Position == null, + OriginalCommitSha = adapter.OriginalCommitId, + OriginalPosition = adapter.OriginalPosition, + Path = adapter.Path, + Position = adapter.Position, + }; + + // Set a reference to the thread in the comment. + foreach (var comment in threadSource.Value) + { + comment.Thread = thread; + } + + threads.Add(thread); + } + + result.Threads = threads; + return result; } @@ -383,7 +711,7 @@ public virtual async Task ReadViewer(HostAddress address) } var connection = await graphqlFactory.CreateConnection(address); - return await connection.Run(readViewer); + return await connection.Run(readViewer, cacheDuration: TimeSpan.FromMinutes(10)); } public async Task GetGraphQLPullRequestId( @@ -391,11 +719,11 @@ public async Task GetGraphQLPullRequestId( string repositoryOwner, int number) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var query = new Query() - .Repository(repositoryOwner, localRepository.Name) + .Repository(owner: repositoryOwner, name: localRepository.Name) .PullRequest(number) .Select(x => x.Id); @@ -450,10 +778,10 @@ public async Task CreatePendingReview( LocalRepositoryModel localRepository, string pullRequestId) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var (_, owner, number) = await CreatePendingReviewCore(localRepository, pullRequestId); - var detail = await ReadPullRequestDetail(address, owner, localRepository.Name, number); + var detail = await ReadPullRequestDetail(address, owner, localRepository.Name, number, true); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentStartReview); @@ -465,7 +793,7 @@ public async Task CancelPendingReview( LocalRepositoryModel localRepository, string reviewId) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var delete = new DeletePullRequestReviewInput @@ -482,7 +810,7 @@ public async Task CancelPendingReview( }); var result = await graphql.Run(mutation); - return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number, true); } /// @@ -493,7 +821,7 @@ public async Task PostReview( string body, PullRequestReviewEvent e) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var addReview = new AddPullRequestReviewInput @@ -514,7 +842,7 @@ public async Task PostReview( var result = await graphql.Run(mutation); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewPosts); - return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number, true); } public async Task SubmitPendingReview( @@ -523,7 +851,7 @@ public async Task SubmitPendingReview( string body, PullRequestReviewEvent e) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var submit = new SubmitPullRequestReviewInput @@ -543,7 +871,7 @@ public async Task SubmitPendingReview( var result = await graphql.Run(mutation); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewPosts); - return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number, true); } /// @@ -555,7 +883,7 @@ public async Task PostPendingReviewComment( string path, int position) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var comment = new AddPullRequestReviewCommentInput @@ -577,7 +905,7 @@ public async Task PostPendingReviewComment( var result = await graphql.Run(addComment); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); - return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number, true); } /// @@ -587,7 +915,7 @@ public async Task PostPendingReviewCommentReply( string body, string inReplyTo) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var comment = new AddPullRequestReviewCommentInput @@ -607,7 +935,7 @@ public async Task PostPendingReviewCommentReply( var result = await graphql.Run(addComment); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); - return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number, true); } /// @@ -619,12 +947,11 @@ public async Task PostStandaloneReviewComment( string path, int position) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var addReview = new AddPullRequestReviewInput { - Body = body, CommitOID = commitId, Event = Octokit.GraphQL.Model.PullRequestReviewEvent.Comment, PullRequestId = new ID(pullRequestId), @@ -649,7 +976,7 @@ public async Task PostStandaloneReviewComment( var result = await graphql.Run(mutation); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); - return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number, true); } /// @@ -671,7 +998,7 @@ public async Task DeleteComment( int pullRequestId, int commentDatabaseId) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var apiClient = await apiClientFactory.Create(address); await apiClient.DeletePullRequestReviewComment( @@ -680,7 +1007,7 @@ await apiClient.DeletePullRequestReviewComment( commentDatabaseId); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentDelete); - return await ReadPullRequestDetail(address, remoteRepositoryOwner, localRepository.Name, pullRequestId); + return await ReadPullRequestDetail(address, remoteRepositoryOwner, localRepository.Name, pullRequestId, true); } /// @@ -689,7 +1016,7 @@ public async Task EditComment(LocalRepositoryModel local string commentNodeId, string body) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var updatePullRequestReviewCommentInput = new UpdatePullRequestReviewCommentInput @@ -707,12 +1034,12 @@ public async Task EditComment(LocalRepositoryModel local var result = await graphql.Run(editComment); await usageTracker.IncrementCounter(x => x.NumberOfPRReviewDiffViewInlineCommentPost); - return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number); + return await ReadPullRequestDetail(address, result.Login, localRepository.Name, result.Number, true); } async Task<(string id, string owner, int number)> CreatePendingReviewCore(LocalRepositoryModel localRepository, string pullRequestId) { - var address = HostAddress.Create(localRepository.CloneUrl.Host); + var address = HostAddress.Create(localRepository.CloneUrl); var graphql = await graphqlFactory.CreateConnection(address); var input = new AddPullRequestReviewInput @@ -749,10 +1076,10 @@ int GetUpdatedLineNumber(IInlineCommentThreadModel thread, IEnumerable GetRepository(LocalRepositoryModel repository) { - return Task.Factory.StartNew(() => gitService.GetRepository(repository.LocalPath)); + return Task.Run(() => gitService.GetRepository(repository.LocalPath)); } - async Task GetPullRequestLastCommitAdapter(HostAddress address, string owner, string name, int number) + async Task GetPullRequestLastCommitAdapter(HostAddress address, string owner, string name, int number, bool refresh) { ICompiledQuery> query; if (address.IsGitHubDotCom()) @@ -760,22 +1087,36 @@ async Task GetPullRequestLastCommitAdapter(HostAddress addres if (readCommitStatuses == null) { readCommitStatuses = new Query() - .Repository(Var(nameof(owner)), Var(nameof(name))) - .PullRequest(Var(nameof(number))).Commits(last: 1).Nodes.Select( + .Repository(owner: Var(nameof(owner)), name: Var(nameof(name))) + .PullRequest(number: Var(nameof(number))).Commits(last: 1).Nodes.Select( commit => new LastCommitAdapter { + HeadSha = commit.Commit.Oid, CheckSuites = commit.Commit.CheckSuites(null, null, null, null, null).AllPages(10) .Select(suite => new CheckSuiteModel { CheckRuns = suite.CheckRuns(null, null, null, null, null).AllPages(10) .Select(run => new CheckRunModel { + Id = run.Id.Value, Conclusion = run.Conclusion.FromGraphQl(), Status = run.Status.FromGraphQl(), Name = run.Name, DetailsUrl = run.Permalink, Summary = run.Summary, - }).ToList() + Text = run.Text, + Annotations = run.Annotations(null, null, null, null).AllPages() + .Select(annotation => new CheckRunAnnotationModel + { + Title = annotation.Title, + Message = annotation.Message, + Path = annotation.Path, + AnnotationLevel = annotation.AnnotationLevel.Value.FromGraphQl(), + StartLine = annotation.Location.Start.Line, + EndLine = annotation.Location.End.Line, + }).ToList() + }).ToList(), + ApplicationName = suite.App != null ? suite.App.Name : "Private App" }).ToList(), Statuses = commit.Commit.Status .Select(context => @@ -784,7 +1125,7 @@ async Task GetPullRequestLastCommitAdapter(HostAddress addres State = statusContext.State.FromGraphQl(), Context = statusContext.Context, TargetUrl = statusContext.TargetUrl, - Description = statusContext.Description, + Description = statusContext.Description }).ToList() ).SingleOrDefault() } @@ -798,20 +1139,22 @@ async Task GetPullRequestLastCommitAdapter(HostAddress addres if (readCommitStatusesEnterprise == null) { readCommitStatusesEnterprise = new Query() - .Repository(Var(nameof(owner)), Var(nameof(name))) - .PullRequest(Var(nameof(number))).Commits(last: 1).Nodes.Select( + .Repository(owner: Var(nameof(owner)), name: Var(nameof(name))) + .PullRequest(number: Var(nameof(number))).Commits(last: 1).Nodes.Select( commit => new LastCommitAdapter { - Statuses = commit.Commit.Status - .Select(context => - context.Contexts.Select(statusContext => new StatusModel - { - State = statusContext.State.FromGraphQl(), - Context = statusContext.Context, - TargetUrl = statusContext.TargetUrl, - Description = statusContext.Description, - }).ToList() - ).SingleOrDefault() + Statuses = commit.Commit.Status == null ? null : commit.Commit.Status + .Select(context => context == null + ? null + : context.Contexts + .Select(statusContext => new StatusModel + { + State = statusContext.State.FromGraphQl(), + Context = statusContext.Context, + TargetUrl = statusContext.TargetUrl, + Description = statusContext.Description, + }).ToList() + ).SingleOrDefault() } ).Compile(); } @@ -827,69 +1170,10 @@ async Task GetPullRequestLastCommitAdapter(HostAddress addres }; var connection = await graphqlFactory.CreateConnection(address); - var result = await connection.Run(query, vars); + var result = await connection.Run(query, vars, refresh); return result.First(); } - static void BuildPullRequestThreads(PullRequestDetailModel model) - { - var commentsByReplyId = new Dictionary>(); - - // Get all comments that are not replies. - foreach (CommentAdapter comment in model.Reviews.SelectMany(x => x.Comments)) - { - if (comment.ReplyTo == null) - { - commentsByReplyId.Add(comment.Id, new List { comment }); - } - } - - // Get the comments that are replies and place them into the relevant list. - foreach (CommentAdapter comment in model.Reviews.SelectMany(x => x.Comments).OrderBy(x => x.CreatedAt)) - { - if (comment.ReplyTo != null) - { - List thread = null; - - if (commentsByReplyId.TryGetValue(comment.ReplyTo, out thread)) - { - thread.Add(comment); - } - } - } - - // Build a collection of threads for the information collected above. - var threads = new List(); - - foreach (var threadSource in commentsByReplyId) - { - var adapter = threadSource.Value[0]; - - var thread = new PullRequestReviewThreadModel - { - Comments = threadSource.Value, - CommitSha = adapter.CommitSha, - DiffHunk = adapter.DiffHunk, - Id = adapter.Id, - IsOutdated = adapter.Position == null, - OriginalCommitSha = adapter.OriginalCommitId, - OriginalPosition = adapter.OriginalPosition, - Path = adapter.Path, - Position = adapter.Position, - }; - - // Set a reference to the thread in the comment. - foreach (var comment in threadSource.Value) - { - comment.Thread = thread; - } - - threads.Add(thread); - } - - model.Threads = threads; - } - static Octokit.GraphQL.Model.PullRequestReviewEvent ToGraphQl(Octokit.PullRequestReviewEvent e) { switch (e) @@ -914,6 +1198,7 @@ class CommentAdapter : PullRequestReviewCommentModel public int OriginalPosition { get; set; } public string OriginalCommitId { get; set; } public string ReplyTo { get; set; } + public string PullRequestReviewId { get; set; } } class LastCommitAdapter @@ -921,6 +1206,8 @@ class LastCommitAdapter public List CheckSuites { get; set; } public List Statuses { get; set; } + + public string HeadSha { get; set; } } - } + } } diff --git a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs index 94583183df..0c5b4acf7f 100644 --- a/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs +++ b/src/GitHub.InlineReviews/Services/PullRequestStatusBarManager.cs @@ -9,6 +9,7 @@ using GitHub.Commands; using GitHub.Extensions; using GitHub.Primitives; +using GitHub.VisualStudio; using GitHub.InlineReviews.Views; using GitHub.InlineReviews.ViewModels; using GitHub.Services; @@ -36,6 +37,7 @@ public class PullRequestStatusBarManager readonly Lazy pullRequestSessionManager; readonly Lazy teamExplorerContext; readonly Lazy connectionManager; + readonly Lazy tippingService; [ImportingConstructor] public PullRequestStatusBarManager( @@ -44,7 +46,8 @@ public PullRequestStatusBarManager( IShowCurrentPullRequestCommand showCurrentPullRequestCommand, Lazy pullRequestSessionManager, Lazy teamExplorerContext, - Lazy connectionManager) + Lazy connectionManager, + Lazy tippingService) { this.openPullRequestsCommand = new UsageTrackingCommand(usageTracker, x => x.NumberOfStatusBarOpenPullRequestList, openPullRequestsCommand); @@ -54,6 +57,7 @@ public PullRequestStatusBarManager( this.pullRequestSessionManager = pullRequestSessionManager; this.teamExplorerContext = teamExplorerContext; this.connectionManager = connectionManager; + this.tippingService = tippingService; } /// @@ -82,6 +86,11 @@ public void StartShowingStatus() async Task RefreshCurrentSession(LocalRepositoryModel repository, IPullRequestSession session) { + if (repository != null && repository.HasRemotesButNoOrigin) + { + NoRemoteOriginCallout(); + } + var showStatus = await IsDotComOrEnterpriseRepository(repository); if (!showStatus) { @@ -89,10 +98,37 @@ async Task RefreshCurrentSession(LocalRepositoryModel repository, IPullRequestSe return; } - var viewModel = CreatePullRequestStatusViewModel(session); + var viewModel = CreatePullRequestStatusViewModel(repository, session); ShowStatus(viewModel); } + [STAThread] + void NoRemoteOriginCallout() + { + try + { + var view = FindSccStatusBar(Application.Current.MainWindow); + if (view == null) + { + log.Warning("Couldn't find SccStatusBar"); + return; + } + + tippingService.Value.RequestCalloutDisplay( + calloutId: Guids.NoRemoteOriginCalloutId, + title: Resources.CantFindGitHubUrlForRepository, + message: Resources.RepositoriesMustHaveRemoteOrigin, + isPermanentlyDismissible: true, + targetElement: view, + vsCommandGroupId: Guids.guidGitHubCmdSet, + vsCommandId: PkgCmdIDList.showGitHubPaneCommand); + } + catch (Exception e) + { + log.Error(e, nameof(NoRemoteOriginCallout)); + } + } + async Task IsDotComOrEnterpriseRepository(LocalRepositoryModel repository) { var cloneUrl = repository?.CloneUrl; @@ -119,16 +155,18 @@ async Task IsDotComOrEnterpriseRepository(LocalRepositoryModel repository) return false; } - PullRequestStatusViewModel CreatePullRequestStatusViewModel(IPullRequestSession session) + PullRequestStatusViewModel CreatePullRequestStatusViewModel(LocalRepositoryModel repository, IPullRequestSession session) { - var pullRequestStatusViewModel = new PullRequestStatusViewModel(openPullRequestsCommand, showCurrentPullRequestCommand); - var pullRequest = session?.PullRequest; - pullRequestStatusViewModel.Number = pullRequest?.Number; - pullRequestStatusViewModel.Title = pullRequest?.Title; - return pullRequestStatusViewModel; + return new PullRequestStatusViewModel(openPullRequestsCommand, showCurrentPullRequestCommand) + { + Number = session?.PullRequest?.Number, + Title = session?.PullRequest?.Title, + RepositoryName = repository?.Name, + RepositoryOwner = repository?.Owner, + }; } - void ShowStatus(PullRequestStatusViewModel pullRequestStatusViewModel = null) + PullRequestStatusView ShowStatus(PullRequestStatusViewModel pullRequestStatusViewModel = null) { var statusBar = FindSccStatusBar(Application.Current.MainWindow); if (statusBar != null) @@ -144,8 +182,11 @@ void ShowStatus(PullRequestStatusViewModel pullRequestStatusViewModel = null) { githubStatusBar = new PullRequestStatusView { DataContext = pullRequestStatusViewModel }; statusBar.Items.Insert(0, githubStatusBar); + return githubStatusBar; } } + + return null; } static T Find(StatusBar statusBar) diff --git a/src/GitHub.InlineReviews/Tags/InlineCommentGlyphFactory.cs b/src/GitHub.InlineReviews/Tags/InlineCommentGlyphFactory.cs index 6d5c4ac613..4dc28ea4b6 100644 --- a/src/GitHub.InlineReviews/Tags/InlineCommentGlyphFactory.cs +++ b/src/GitHub.InlineReviews/Tags/InlineCommentGlyphFactory.cs @@ -56,12 +56,25 @@ static UserControl CreateGlyph(InlineCommentTag tag) { return new AddInlineCommentGlyph(); } - else if (showTag != null) + + if (showTag != null) { - return new ShowInlineCommentGlyph() + if (showTag.Thread != null && showTag.Annotations != null) + { + return new ShowInlineCommentAnnotationGlyph(); + } + + if (showTag.Thread != null) { - Opacity = showTag.Thread.IsStale ? 0.5 : 1, - }; + return new ShowInlineCommentGlyph(); + } + + if (showTag.Annotations != null) + { + return new ShowInlineAnnotationGlyph(); + } + + throw new ArgumentException($"{nameof(showTag)} does not have a thread or annotations"); } throw new ArgumentException($"Unknown 'InlineCommentTag' type '{tag}'"); diff --git a/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs b/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs index 3891003d63..643433c982 100644 --- a/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs +++ b/src/GitHub.InlineReviews/Tags/InlineCommentTagger.cs @@ -84,24 +84,73 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCol { var startLine = span.Start.GetContainingLine().LineNumber; var endLine = span.End.GetContainingLine().LineNumber; - var linesWithComments = new BitArray((endLine - startLine) + 1); + var linesWithTags = new BitArray((endLine - startLine) + 1); var spanThreads = file.InlineCommentThreads.Where(x => x.LineNumber >= startLine && - x.LineNumber <= endLine); + x.LineNumber <= endLine) + .ToArray(); - foreach (var thread in spanThreads) + var spanThreadsByLine = spanThreads.ToDictionary(model => model.LineNumber); + + Dictionary spanAnnotationsByLine = null; + if (side == DiffSide.Right) + { + var spanAnnotations = file.InlineAnnotations?.Where(x => + x.EndLine - 1 >= startLine && + x.EndLine - 1 <= endLine); + + spanAnnotationsByLine = spanAnnotations?.GroupBy(model => model.EndLine) + .ToDictionary(models => models.Key - 1, models => models.ToArray()); + } + + var lines = spanThreadsByLine.Keys.Union(spanAnnotationsByLine?.Keys ?? Enumerable.Empty()); + foreach (var line in lines) { var snapshot = span.Snapshot; - var line = snapshot.GetLineFromLineNumber(thread.LineNumber); + var snapshotLine = snapshot.GetLineFromLineNumber(line); + + if (spanThreadsByLine.TryGetValue(line, out var thread)) + { + var isThreadDeleteSide = thread.DiffLineType == DiffChangeType.Delete; + var sidesMatch = side == DiffSide.Left && isThreadDeleteSide || side == DiffSide.Right && !isThreadDeleteSide; + if (!sidesMatch) + { + thread = null; + } + } - if ((side == DiffSide.Left && thread.DiffLineType == DiffChangeType.Delete) || - (side == DiffSide.Right && thread.DiffLineType != DiffChangeType.Delete)) + InlineAnnotationModel[] annotations = null; + spanAnnotationsByLine?.TryGetValue(line, out annotations); + + if (thread != null || annotations != null) { - linesWithComments[thread.LineNumber - startLine] = true; + linesWithTags[line - startLine] = true; + + CheckAnnotationLevel? summaryAnnotationLevel = null; + if (annotations != null) + { + var hasFailure = annotations.Any(model => model.AnnotationLevel == CheckAnnotationLevel.Failure); + if (hasFailure) + { + summaryAnnotationLevel = CheckAnnotationLevel.Failure; + } + else + { + var hasWarning = annotations.Any(model => model.AnnotationLevel == CheckAnnotationLevel.Warning); + summaryAnnotationLevel = hasWarning ? CheckAnnotationLevel.Warning : CheckAnnotationLevel.Notice; + } + } + + var showInlineTag = new ShowInlineCommentTag(currentSession, line, thread?.DiffLineType ?? DiffChangeType.Add) + { + Thread = thread, + Annotations = annotations, + SummaryAnnotationLevel = summaryAnnotationLevel, + }; result.Add(new TagSpan( - new SnapshotSpan(line.Start, line.End), - new ShowInlineCommentTag(currentSession, thread))); + new SnapshotSpan(snapshotLine.Start, snapshotLine.End), + showInlineTag)); } } @@ -113,7 +162,7 @@ public IEnumerable> GetTags(NormalizedSnapshotSpanCol if (lineNumber >= startLine && lineNumber <= endLine && - !linesWithComments[lineNumber - startLine] + !linesWithTags[lineNumber - startLine] && (side == DiffSide.Right || line.Type == DiffChangeType.Delete)) { var snapshotLine = span.Snapshot.GetLineFromLineNumber(lineNumber); diff --git a/src/GitHub.InlineReviews/Tags/InlineCommentTaggerProvider.cs b/src/GitHub.InlineReviews/Tags/InlineCommentTaggerProvider.cs index fbdb02ab7d..2aec8df74e 100644 --- a/src/GitHub.InlineReviews/Tags/InlineCommentTaggerProvider.cs +++ b/src/GitHub.InlineReviews/Tags/InlineCommentTaggerProvider.cs @@ -1,9 +1,9 @@ using System; using System.ComponentModel.Composition; using GitHub.Extensions; -using GitHub.InlineReviews.Services; using GitHub.Services; using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Differencing; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Tagging; using Microsoft.VisualStudio.Utilities; @@ -16,6 +16,9 @@ namespace GitHub.InlineReviews.Tags [Export(typeof(IViewTaggerProvider))] [ContentType("text")] [TagType(typeof(ShowInlineCommentTag))] + [TextViewRole("LEFTDIFF")] + [TextViewRole("RIGHTDIFF")] + [TextViewRole("INLINEDIFF")] class InlineCommentTaggerProvider : IViewTaggerProvider { readonly IPullRequestSessionManager sessionManager; @@ -31,11 +34,22 @@ public InlineCommentTaggerProvider( public ITagger CreateTagger(ITextView view, ITextBuffer buffer) where T : ITag { - return buffer.Properties.GetOrCreateSingletonProperty(() => - new InlineCommentTagger( - view, - buffer, - sessionManager)) as ITagger; + if (view.TextViewModel is IDifferenceTextViewModel model) + { + if (buffer == model.Viewer.DifferenceBuffer.BaseLeftBuffer) + { + return view.Properties.GetOrCreateSingletonProperty("InlineTaggerForLeftBuffer", + () => new InlineCommentTagger(view, buffer, sessionManager) as ITagger); + } + + if (buffer == model.Viewer.DifferenceBuffer.BaseRightBuffer) + { + return view.Properties.GetOrCreateSingletonProperty("InlineTaggerForRightBuffer", + () => new InlineCommentTagger(view, buffer, sessionManager) as ITagger); + } + } + + return null; } } } diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineAnnotationGlyph.xaml b/src/GitHub.InlineReviews/Tags/ShowInlineAnnotationGlyph.xaml new file mode 100644 index 0000000000..1eae97fa11 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/ShowInlineAnnotationGlyph.xaml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineAnnotationGlyph.xaml.cs b/src/GitHub.InlineReviews/Tags/ShowInlineAnnotationGlyph.xaml.cs new file mode 100644 index 0000000000..08f998aaf0 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/ShowInlineAnnotationGlyph.xaml.cs @@ -0,0 +1,13 @@ +using System; +using System.Windows.Controls; + +namespace GitHub.InlineReviews.Tags +{ + public partial class ShowInlineAnnotationGlyph : UserControl + { + public ShowInlineAnnotationGlyph() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineCommentAnnotationGlyph.xaml b/src/GitHub.InlineReviews/Tags/ShowInlineCommentAnnotationGlyph.xaml new file mode 100644 index 0000000000..5d18e67537 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/ShowInlineCommentAnnotationGlyph.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineCommentAnnotationGlyph.xaml.cs b/src/GitHub.InlineReviews/Tags/ShowInlineCommentAnnotationGlyph.xaml.cs new file mode 100644 index 0000000000..1523f19280 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/ShowInlineCommentAnnotationGlyph.xaml.cs @@ -0,0 +1,13 @@ +using System; +using System.Windows.Controls; + +namespace GitHub.InlineReviews.Tags +{ + public partial class ShowInlineCommentAnnotationGlyph : UserControl + { + public ShowInlineCommentAnnotationGlyph() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml b/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml index 77e7386777..b147cfab84 100644 --- a/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml +++ b/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml @@ -9,16 +9,34 @@ - - - + + + + + + diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml.cs b/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml.cs index 50dd329d61..0bae7c2d9b 100644 --- a/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml.cs +++ b/src/GitHub.InlineReviews/Tags/ShowInlineCommentGlyph.xaml.cs @@ -9,6 +9,5 @@ public ShowInlineCommentGlyph() { InitializeComponent(); } - } } diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineCommentTag.cs b/src/GitHub.InlineReviews/Tags/ShowInlineCommentTag.cs index b1071754cf..6c384ad014 100644 --- a/src/GitHub.InlineReviews/Tags/ShowInlineCommentTag.cs +++ b/src/GitHub.InlineReviews/Tags/ShowInlineCommentTag.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using GitHub.Extensions; using GitHub.Models; using GitHub.Services; @@ -14,20 +15,26 @@ public class ShowInlineCommentTag : InlineCommentTag /// Initializes a new instance of the class. /// /// The pull request session. - /// A model holding the details of the thread. - public ShowInlineCommentTag( - IPullRequestSession session, - IInlineCommentThreadModel thread) - : base(session, thread.LineNumber, thread.DiffLineType) + /// 0-based index of the inline tag + /// The diff type for the inline comment + public ShowInlineCommentTag(IPullRequestSession session, int lineNumber, DiffChangeType diffLineType) + : base(session, lineNumber, diffLineType) { - Guard.ArgumentNotNull(thread, nameof(thread)); - - Thread = thread; } /// /// Gets a model holding details of the thread at the tagged line. /// - public IInlineCommentThreadModel Thread { get; } + public IInlineCommentThreadModel Thread { get; set; } + + /// + /// Gets a list of models holding details of the annotations at the tagged line. + /// + public IReadOnlyList Annotations { get; set; } + + /// + /// Gets a summary annotation level is Annotations are present + /// + public CheckAnnotationLevel? SummaryAnnotationLevel { get; set; } } } diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineGlyph.xaml b/src/GitHub.InlineReviews/Tags/ShowInlineGlyph.xaml new file mode 100644 index 0000000000..cf5726b99d --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/ShowInlineGlyph.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + diff --git a/src/GitHub.InlineReviews/Tags/ShowInlineGlyph.xaml.cs b/src/GitHub.InlineReviews/Tags/ShowInlineGlyph.xaml.cs new file mode 100644 index 0000000000..e8b5ac4a68 --- /dev/null +++ b/src/GitHub.InlineReviews/Tags/ShowInlineGlyph.xaml.cs @@ -0,0 +1,14 @@ +using System; +using System.Windows.Controls; + +namespace GitHub.InlineReviews.Tags +{ + public partial class ShowInlineGlyph : UserControl + { + public ShowInlineGlyph() + { + InitializeComponent(); + } + + } +} diff --git a/src/GitHub.InlineReviews/VSPackage.resx b/src/GitHub.InlineReviews/VSPackage.resx index b534be634e..1af7de150c 100644 --- a/src/GitHub.InlineReviews/VSPackage.resx +++ b/src/GitHub.InlineReviews/VSPackage.resx @@ -117,14 +117,4 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - GitHub.InlineReviews - - - A Visual Studio Extension that brings the GitHub Flow into Visual Studio. - - - - resources\logo_32x32@2x.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a - \ No newline at end of file diff --git a/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs b/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs index efaf81971d..2d967989dc 100644 --- a/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs +++ b/src/GitHub.InlineReviews/ViewModels/InlineCommentPeekViewModel.cs @@ -33,12 +33,14 @@ public sealed class InlineCommentPeekViewModel : ReactiveObject, IDisposable IPullRequestSession session; IPullRequestSessionFile file; IPullRequestReviewCommentThreadViewModel thread; + IReadOnlyList annotations; IDisposable fileSubscription; IDisposable sessionSubscription; IDisposable threadSubscription; ITrackingPoint triggerPoint; string relativePath; DiffSide side; + bool availableForComment; /// /// Initializes a new instance of the class. @@ -86,6 +88,21 @@ public InlineCommentPeekViewModel(IInlineCommentPeekService peekService, Observable.Return(previousCommentCommand.Enabled)); } + public bool AvailableForComment + { + get { return availableForComment; } + private set { this.RaiseAndSetIfChanged(ref availableForComment, value); } + } + + /// + /// Gets the annotations displayed. + /// + public IReadOnlyList Annotations + { + get { return annotations; } + private set { this.RaiseAndSetIfChanged(ref annotations, value); } + } + /// /// Gets the thread of comments to display. /// @@ -143,10 +160,10 @@ public async Task Initialize() } fileSubscription?.Dispose(); - fileSubscription = file.LinesChanged.ObserveOn(RxApp.MainThreadScheduler).Subscribe(LinesChanged); + fileSubscription = file.LinesChanged.ObserveOn(RxApp.MainThreadScheduler).Subscribe(x => LinesChanged(x).Forget()); } - async void LinesChanged(IReadOnlyList> lines) + async Task LinesChanged(IReadOnlyList> lines) { try { @@ -168,27 +185,41 @@ async Task UpdateThread() Thread = null; threadSubscription?.Dispose(); + Annotations = null; + if (file == null) return; var lineAndLeftBuffer = peekService.GetLineNumber(peekSession, triggerPoint); var lineNumber = lineAndLeftBuffer.Item1; var leftBuffer = lineAndLeftBuffer.Item2; + + AvailableForComment = + file.Diff.Any(chunk => chunk.Lines + .Any(line => leftBuffer ? + line.OldLineNumber - 1 == lineNumber : + line.NewLineNumber - 1 == lineNumber)); + var thread = file.InlineCommentThreads?.FirstOrDefault(x => x.LineNumber == lineNumber && ((leftBuffer && x.DiffLineType == DiffChangeType.Delete) || (!leftBuffer && x.DiffLineType != DiffChangeType.Delete))); - var vm = factory.CreateViewModel(); + + Annotations = file.InlineAnnotations?.Where(model => model.EndLine - 1 == lineNumber) + .Select(model => new InlineAnnotationViewModel(model)) + .ToArray(); + + var threadModel = factory.CreateViewModel(); if (thread?.Comments.Count > 0) { - await vm.InitializeAsync(session, file, thread, true); + await threadModel.InitializeAsync(session, file, thread, true); } else { - await vm.InitializeNewAsync(session, file, lineNumber, side, true); + await threadModel.InitializeNewAsync(session, file, lineNumber, side, true); } - Thread = vm; + Thread = threadModel; } async Task SessionChanged(IPullRequestSession pullRequestSession) diff --git a/src/GitHub.InlineReviews/ViewModels/PullRequestFileMarginViewModel.cs b/src/GitHub.InlineReviews/ViewModels/PullRequestFileMarginViewModel.cs deleted file mode 100644 index 84968d9d85..0000000000 --- a/src/GitHub.InlineReviews/ViewModels/PullRequestFileMarginViewModel.cs +++ /dev/null @@ -1,53 +0,0 @@ -using System; -using System.Windows.Input; -using GitHub.Commands; -using GitHub.Services; -using ReactiveUI; - -namespace GitHub.InlineReviews.ViewModels -{ - public class PullRequestFileMarginViewModel : ReactiveObject - { - bool enabled; - string fileName; - int commentsInFile; - bool marginEnabled; - - public PullRequestFileMarginViewModel(ICommand toggleInlineCommentMarginCommand, ICommand viewChangesCommand, - Lazy usageTracker) - { - ToggleInlineCommentMarginCommand = toggleInlineCommentMarginCommand = new UsageTrackingCommand( - usageTracker, x => x.NumberOfPullRequestFileMarginToggleInlineCommentMargin, toggleInlineCommentMarginCommand); - ViewChangesCommand = viewChangesCommand = new UsageTrackingCommand( - usageTracker, x => x.NumberOfPullRequestFileMarginViewChanges, viewChangesCommand); - } - - public bool Enabled - { - get { return enabled; } - set { this.RaiseAndSetIfChanged(ref enabled, value); } - } - - public string FileName - { - get { return fileName; } - set { this.RaiseAndSetIfChanged(ref fileName, value); } - } - - public int CommentsInFile - { - get { return commentsInFile; } - set { this.RaiseAndSetIfChanged(ref commentsInFile, value); } - } - - public bool MarginEnabled - { - get { return marginEnabled; } - set { this.RaiseAndSetIfChanged(ref marginEnabled, value); } - } - - public ICommand ToggleInlineCommentMarginCommand { get; } - - public ICommand ViewChangesCommand { get; } - } -} diff --git a/src/GitHub.InlineReviews/ViewModels/PullRequestStatusViewModel.cs b/src/GitHub.InlineReviews/ViewModels/PullRequestStatusViewModel.cs index 9c59ad2379..6e4db9f6db 100644 --- a/src/GitHub.InlineReviews/ViewModels/PullRequestStatusViewModel.cs +++ b/src/GitHub.InlineReviews/ViewModels/PullRequestStatusViewModel.cs @@ -8,6 +8,8 @@ public class PullRequestStatusViewModel : INotifyPropertyChanged { int? number; string title; + string repositoryName; + string repositoryOwner; public PullRequestStatusViewModel(ICommand openPullRequestsCommand, ICommand showCurrentPullRequestCommand) { @@ -41,6 +43,32 @@ public string Title } } + public string RepositoryOwner + { + get { return repositoryOwner; } + set + { + if (repositoryOwner != value) + { + repositoryOwner = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RepositoryOwner))); + } + } + } + + public string RepositoryName + { + get { return repositoryName; } + set + { + if (repositoryName != value) + { + repositoryName = value; + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(RepositoryName))); + } + } + } + public ICommand OpenPullRequestsCommand { get; } public ICommand ShowCurrentPullRequestCommand { get; } diff --git a/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml b/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml index e8a7a4269a..06bfd3a199 100644 --- a/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml +++ b/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml @@ -3,10 +3,12 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:local="clr-namespace:GitHub.InlineReviews.Views" xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:ghfvs="https://github.com/github/VisualStudio" + xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" + xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog" mc:Ignorable="d" d:DesignHeight="200" d:DesignWidth="500"> @@ -101,12 +103,42 @@ + + + + + + + + + + - - - + + + + + + + + + + + diff --git a/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml.cs b/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml.cs index 77e1511a6d..10cc2c5e06 100644 --- a/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml.cs +++ b/src/GitHub.InlineReviews/Views/InlineCommentPeekView.xaml.cs @@ -15,17 +15,19 @@ public InlineCommentPeekView() InitializeComponent(); desiredHeight = new Subject(); - threadView.LayoutUpdated += ThreadViewLayoutUpdated; + threadView.LayoutUpdated += ChildLayoutUpdated; + annotationsView.LayoutUpdated += ChildLayoutUpdated; threadScroller.PreviewMouseWheel += ScrollViewerUtilities.FixMouseWheelScroll; } public IObservable DesiredHeight => desiredHeight; - void ThreadViewLayoutUpdated(object sender, EventArgs e) + void ChildLayoutUpdated(object sender, EventArgs e) { var otherControlsHeight = ActualHeight - threadScroller.ActualHeight; var threadViewHeight = threadView.DesiredSize.Height + threadView.Margin.Top + threadView.Margin.Bottom; - desiredHeight.OnNext(threadViewHeight + otherControlsHeight); + var annotationsViewHeight = annotationsView.DesiredSize.Height + annotationsView.Margin.Top + annotationsView.Margin.Bottom; + desiredHeight.OnNext(threadViewHeight + annotationsViewHeight + otherControlsHeight); } } } diff --git a/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml b/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml deleted file mode 100644 index c6a0543ea9..0000000000 --- a/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml.cs b/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml.cs deleted file mode 100644 index 9dda80ff8b..0000000000 --- a/src/GitHub.InlineReviews/Views/PullRequestFileMarginView.xaml.cs +++ /dev/null @@ -1,13 +0,0 @@ -using System; -using System.Windows.Controls; - -namespace GitHub.InlineReviews.Views -{ - public partial class PullRequestFileMarginView : UserControl - { - public PullRequestFileMarginView() - { - InitializeComponent(); - } - } -} diff --git a/src/GitHub.InlineReviews/Views/PullRequestStatusView.xaml b/src/GitHub.InlineReviews/Views/PullRequestStatusView.xaml index 4d73324803..884ba67fed 100644 --- a/src/GitHub.InlineReviews/Views/PullRequestStatusView.xaml +++ b/src/GitHub.InlineReviews/Views/PullRequestStatusView.xaml @@ -49,7 +49,10 @@ Fill="White" VerticalAlignment="Bottom" Margin="0 0 4 0 " - Icon="git_pull_request" /> + Icon="mark_github" /> + + / + diff --git a/src/GitHub.InlineReviews/packages.config b/src/GitHub.InlineReviews/packages.config deleted file mode 100644 index 68889b5182..0000000000 --- a/src/GitHub.InlineReviews/packages.config +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/GitHub.Logging/Logging/ILoggerExtensions.cs b/src/GitHub.Logging/Logging/ILoggerExtensions.cs index 3d61316ab2..aea5ca98d7 100644 --- a/src/GitHub.Logging/Logging/ILoggerExtensions.cs +++ b/src/GitHub.Logging/Logging/ILoggerExtensions.cs @@ -1,3 +1,6 @@ +using System; +using System.Globalization; +using System.Threading.Tasks; using Serilog; namespace GitHub.Logging @@ -25,5 +28,37 @@ public static void Assert(this ILogger logger, bool condition, string messageTem #pragma warning restore Serilog004 } } + + public static void Time(this ILogger logger, string name, Action method) + { + var startTime = DateTime.Now; + method(); + logger.Verbose("{Name} took {Seconds} seconds", name, FormatSeconds(DateTime.Now - startTime)); + } + + public static T Time(this ILogger logger, string name, Func method) + { + var startTime = DateTime.Now; + var value = method(); + logger.Verbose("{Name} took {Seconds} seconds", name, FormatSeconds(DateTime.Now - startTime)); + return value; + } + + public static async Task TimeAsync(this ILogger logger, string name, Func methodAsync) + { + var startTime = DateTime.Now; + await methodAsync().ConfigureAwait(false); + logger.Verbose("{Name} took {Seconds} seconds", name, FormatSeconds(DateTime.Now - startTime)); + } + + public static async Task TimeAsync(this ILogger logger, string name, Func> methodAsync) + { + var startTime = DateTime.Now; + var value = await methodAsync().ConfigureAwait(false); + logger.Verbose("{Name} took {Seconds} seconds", name, FormatSeconds(DateTime.Now - startTime)); + return value; + } + + static string FormatSeconds(TimeSpan timeSpan) => timeSpan.TotalSeconds.ToString("0.##", CultureInfo.InvariantCulture); } -} \ No newline at end of file +} diff --git a/src/GitHub.Resources/Resources.Designer.cs b/src/GitHub.Resources/Resources.Designer.cs index ff8f5b2516..0db38b220b 100644 --- a/src/GitHub.Resources/Resources.Designer.cs +++ b/src/GitHub.Resources/Resources.Designer.cs @@ -60,6 +60,15 @@ internal Resources() { } } + /// + /// Looks up a localized string similar to Add/Change Accounts. + /// + public static string AddChangeAccounts { + get { + return ResourceManager.GetString("AddChangeAccounts", resourceCulture); + } + } + /// /// Looks up a localized string similar to add. /// @@ -69,6 +78,15 @@ public static string AddedFileStatus { } } + /// + /// Looks up a localized string similar to added some commits. + /// + public static string AddedSomeCommits { + get { + return ResourceManager.GetString("AddedSomeCommits", resourceCulture); + } + } + /// /// Looks up a localized string similar to Add review comment. /// @@ -96,6 +114,15 @@ public static string AddYourReview { } } + /// + /// Looks up a localized string similar to and others. + /// + public static string AndOthers { + get { + return ResourceManager.GetString("AndOthers", resourceCulture); + } + } + /// /// Looks up a localized string similar to Approve. /// @@ -250,11 +277,11 @@ public static string CancelPendingReviewConfirmationCaption { } /// - /// Looks up a localized string similar to There is already a directory at this location, but it doesn't contain a repository.. + /// Looks up a localized string similar to Can't find GitHub URL for repository. /// - public static string CantFindARepositoryAtLocalPath { + public static string CantFindGitHubUrlForRepository { get { - return ResourceManager.GetString("CantFindARepositoryAtLocalPath", resourceCulture); + return ResourceManager.GetString("CantFindGitHubUrlForRepository", resourceCulture); } } @@ -268,7 +295,7 @@ public static string ChangesCountFormat { } /// - /// Looks up a localized string similar to This file has changed since the permalink was created. + /// Looks up a localized string similar to The working file is different to the file at '{0}'. Please checkout the corresponding branch, pull request or commit.. /// public static string ChangesInWorkingDirectoryMessage { get { @@ -295,11 +322,38 @@ public static string CloneLink { } /// - /// Looks up a localized string similar to Clone a Repository. + /// Looks up a localized string similar to Close and comment. /// - public static string CloneTitle { + public static string CloseAndComment { get { - return ResourceManager.GetString("CloneTitle", resourceCulture); + return ResourceManager.GetString("CloseAndComment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close issue. + /// + public static string CloseIssue { + get { + return ResourceManager.GetString("CloseIssue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Close pull request. + /// + public static string ClosePullRequest { + get { + return ResourceManager.GetString("ClosePullRequest", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Comment. + /// + public static string Comment { + get { + return ResourceManager.GetString("Comment", resourceCulture); } } @@ -330,6 +384,15 @@ public static string Comments { } } + /// + /// Looks up a localized string similar to {0} commits. + /// + public static string CommitCountFormat { + get { + return ResourceManager.GetString("CommitCountFormat", resourceCulture); + } + } + /// /// Looks up a localized string similar to Compare File as Default Action. /// @@ -375,6 +438,15 @@ public static string couldNotConnectToTheServerText { } } + /// + /// Looks up a localized string similar to Couldn't find file corresponding to '{0}' in the repository. Please do a 'git fetch' or checkout the target pull request.. + /// + public static string CouldntFindCorrespondingFile { + get { + return ResourceManager.GetString("CouldntFindCorrespondingFile", resourceCulture); + } + } + /// /// Looks up a localized string similar to Couldn't find Git.exe on PATH. /// @@ -531,6 +603,15 @@ public static string DifferentRepositoryMessage { } } + /// + /// Looks up a localized string similar to The directory at the destination path is not empty.. + /// + public static string DirectoryAtDestinationNotEmpty { + get { + return ResourceManager.GetString("DirectoryAtDestinationNotEmpty", resourceCulture); + } + } + /// /// Looks up a localized string similar to Don’t have an account? . /// @@ -874,7 +955,7 @@ public static string LocalBranchUpToDate { } /// - /// Looks up a localized string similar to Local path. + /// Looks up a localized string similar to Local path:. /// public static string localPathText { get { @@ -1008,6 +1089,15 @@ public static string months { } } + /// + /// Looks up a localized string similar to {0} most recently pushed. + /// + public static string MostRecentlyPushed { + get { + return ResourceManager.GetString("MostRecentlyPushed", resourceCulture); + } + } + /// /// Looks up a localized string similar to You must pull before you can push. /// @@ -1215,6 +1305,33 @@ public static string OpenFileInSolution { } } + /// + /// Looks up a localized string similar to Browse.... + /// + public static string OpenFromGitHubBrowse { + get { + return ResourceManager.GetString("OpenFromGitHubBrowse", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Clone. + /// + public static string OpenFromGitHubClone { + get { + return ResourceManager.GetString("OpenFromGitHubClone", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Search or enter a URL. + /// + public static string OpenFromGitHubSearchOrEnterAUrl { + get { + return ResourceManager.GetString("OpenFromGitHubSearchOrEnterAUrl", resourceCulture); + } + } + /// /// Looks up a localized string similar to Open from GitHub. /// @@ -1305,15 +1422,6 @@ public static string Options_ExperimentalTitle { } } - /// - /// Looks up a localized string similar to Show Fork button in Team Explorer. - /// - public static string Options_ForkButtonLabel { - get { - return ResourceManager.GetString("Options_ForkButtonLabel", resourceCulture); - } - } - /// /// Looks up a localized string similar to Help us improve by sending anonymous usage data. /// @@ -1431,15 +1539,6 @@ public static string publishText { } } - /// - /// Looks up a localized string similar to Publish repository. - /// - public static string PublishTitle { - get { - return ResourceManager.GetString("PublishTitle", resourceCulture); - } - } - /// /// Looks up a localized string similar to Publish to GitHub. /// @@ -1449,15 +1548,6 @@ public static string PublishToGitHubButton { } } - /// - /// Looks up a localized string similar to Publish repository to {0}. - /// - public static string PublishToTitle { - get { - return ResourceManager.GetString("PublishToTitle", resourceCulture); - } - } - /// /// Looks up a localized string similar to Pull. /// @@ -1593,6 +1683,33 @@ public static string RenamedFileStatus { } } + /// + /// Looks up a localized string similar to Reopen and comment. + /// + public static string ReopenAndComment { + get { + return ResourceManager.GetString("ReopenAndComment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reopen issue. + /// + public static string ReopenIssue { + get { + return ResourceManager.GetString("ReopenIssue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Reopen pull request. + /// + public static string ReopenPullRequest { + get { + return ResourceManager.GetString("ReopenPullRequest", resourceCulture); + } + } + /// /// Looks up a localized string similar to This repository does not have a remote. Fill out the form to publish it to GitHub.. /// @@ -1612,11 +1729,20 @@ public static string RepoNameText { } /// - /// Looks up a localized string similar to No selected repository.. + /// Looks up a localized string similar to Repositories must have a remote called "origin" defined in order to locate their GitHub URL.. + /// + public static string RepositoriesMustHaveRemoteOrigin { + get { + return ResourceManager.GetString("RepositoriesMustHaveRemoteOrigin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Please rename one of your existing remotes to 'origin' or add a new remote named 'origin' and fetch. This can be done from the command line or by clicking the button below.. /// - public static string RepositoryCloneFailedNoSelectedRepo { + public static string RepositoriesMustHaveRemoteOriginHowToFix { get { - return ResourceManager.GetString("RepositoryCloneFailedNoSelectedRepo", resourceCulture); + return ResourceManager.GetString("RepositoriesMustHaveRemoteOriginHowToFix", resourceCulture); } } @@ -1719,6 +1845,33 @@ public static string RepositoryPublishedMessage { } } + /// + /// Looks up a localized string similar to Collaborator repositories. + /// + public static string RepositorySelectCollaboratorRepositories { + get { + return ResourceManager.GetString("RepositorySelectCollaboratorRepositories", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Contributed to repositories. + /// + public static string RepositorySelectContributedRepositories { + get { + return ResourceManager.GetString("RepositorySelectContributedRepositories", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Your repositories. + /// + public static string RepositorySelectYourRepositories { + get { + return ResourceManager.GetString("RepositorySelectYourRepositories", resourceCulture); + } + } + /// /// Looks up a localized string similar to Request changes. /// @@ -1946,6 +2099,15 @@ public static string ThereArenTAnyOpenPullRequests { } } + /// + /// Looks up a localized string similar to This conversation was marked as resolved. + /// + public static string ThisConversationWasMarkedAsResolved { + get { + return ResourceManager.GetString("ThisConversationWasMarkedAsResolved", resourceCulture); + } + } + /// /// Looks up a localized string similar to Title (required). /// diff --git a/src/GitHub.Resources/Resources.cs-CZ.resx b/src/GitHub.Resources/Resources.cs-CZ.resx new file mode 100644 index 0000000000..70d0994fc7 --- /dev/null +++ b/src/GitHub.Resources/Resources.cs-CZ.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Vyberte nadřazenou složku pro nové úložiště. + + + Otevřít z GitHubu + + + Nepodařilo se připojit ke github.com. + + + Vytvořit gist GitHubu + + + Vytvořit úložiště {0} + + + GistFromVisualStudio.cs + + + Zadejte prosím adresu URL severu Enterprise. + + + Zadejte prosím platnou adresu URL severu Enterprise. + + + Nejedná se o server Enterprise. Zadejte prosím adresu URL serveru Enterprise. + + + (zapomněli jste heslo?) + + + K přihlášení použijte heslo, ne token PAT. + + + Zkontrolujte svoje uživatelské jméno a heslo a zkuste to znovu. + + + Přihlášení se nepovedlo. + + + Připojit ke GitHubu + + + Zadejte prosím heslo. + + + Žádost o přijetí změn pro větev **{0}** se úspěšně vytvořila v [{1}]({2}) + + + Zadejte prosím název žádosti o přijetí změn. + + + Zdrojová a cílová větev nemůžou být stejné. + + + Zdrojová větev neexistuje vzdáleně, nasdíleli jste ji? + + + Zadejte prosím cestu k úložišti. + + + Zadejte prosím platnou cestu. + + + Cesta obsahuje neplatné znaky. + + + Cesta je moc dlouhá. + + + Úložiště {0}/{1} už existuje. + + + Změňte název úložiště nebo vyberte jiný účet a zkuste to znovu. + + + Překročila se kvóta pro privátní úložiště. + + + Úložiště se stejným názvem už v tomto umístění existuje. + + + Zadejte prosím název úložiště. + + + Název úložiště musí mít méně než 100 znaků. + + + Vytvoří se jako {0}. + + + Otevřete aplikaci pro dvoufaktorové ověřování na vašem zařízení a zobrazte svůj ověřovací kód. + + + Poslali jsme vám SMS s vaším ověřovacím kódem. + + + Vyžaduje se dvoufaktorové ověřování. + + + Sem zadejte ověřovací kód pro přihlášení. + + + Zadejte prosím svoje uživatelské jméno nebo e-mailovou adresu. + + + Uživatelské jméno nebo e-mailová adresa nesmí obsahovat mezery. + + + Žádosti o přijetí změn + + + Žádost o přijetí změn + + + přidat + + + fork + + + [neplatný] + + + Před nasdílením změn musíte napřed přijmout změny. + + + Žádné zápisy k přijetí + + + Žádné zápisy k nasdílení + + + *Není zadaný žádný popis.* + + + Rezervovat {0} + + + Rezervovat pro {0} + + + Přijmout změny z větve {0} {1} + + + Nasdílet změny do větve {0} {1} + + + vzdálené úložiště + + + přejmenovat + + + Úložiště zdrojového kódu už není k dispozici. + + + Nelze rezervovat, protože pracovní adresář obsahuje nezapsané změny. + + + Synchronizovat dílčí moduly {0} + + + Git.exe se na zadané cestě nepodařilo najít. + +Nainstalujte si prosím Git pro Windows z tohoto umístění: +https://git-scm.com/download/win + + + Schválené + + + Požadované změny + + + Okomentované + + + Probíhá zpracování. + + + Pokud chcete přejít do editoru, stiskněte Enter. + + + Před přechodem do editoru napřed rezervujte větev PR. + + + Pokud chcete přejít do editoru, stiskněte Enter (větev PR musí být rezervovaná). + + + Fork úložiště + + + Přepnout úložiště Origin + + + Opravdu chcete tuto revizi zrušit? Všechny vaše čekající komentáře se ztratí. + + + Zrušit revizi + + + Soubor na cílové cestě existuje. + + + Vyžaduje se odhlášení. + + + před {0:N0} dnem + + + před {0:N0} dny + + + před {0:N0} hodinou + + + před {0:N0} hodinami + + + právě teď + + + před {0:N0} minutou + + + před {0:N0} minutami + + + před {0:N0} měsícem + + + před {0:N0} měsíci + + + před {0:N0} sekundou + + + před {0:N0} sekundami + + + před {0:N0} rokem + + + před {0:N0} roky + + + Neplatný ověřovací kód + + + Zkuste zadat kód znovu nebo kliknutím na tlačítko pro opětovné odeslání získejte nový ověřovací kód. + + + Ověřovací kód se odeslal. + + + Pokud ověřovací kód nedostanete, obraťte se na support@github.com. + + + Procházet + + + Nepodařilo se připojit ke github.com. + + + Nepodařilo se připojit k serveru. + + + Vytvořit + + + Popis (nepovinné) + + + Otevřít v prohlížeči + + + Zrušit + + + Gist se vytvořil. + + + Gist se nepodařilo vytvořit. + + + podle + + + Ochrana osobních údajů + + + Pomozte nám s vylepšováním posíláním anonymních dat o používání + + + Zkopírování do schránky se nepodařilo. Zkuste to prosím znovu. + + + Odkaz se zkopíroval do schránky. + + + Úložiště se úspěšně vytvořilo. + + + Privátní gist + + + Název souboru + + + Nejste přihlášeni k {0}, takže některé operace git se asi nezdaří. [Přihlásit]({1}) + + + Wiki + + + Impulz + + + Cesta + + + Problémy + + + Grafy + + + Publikovat na GitHub + + + Výkonná spolupráce, revize kódu a správa kódu pro open source a privátní projekty. + + + Připojit… + + + Klonovat + + + Ověřit + + + Dvoufaktorové ověřování + + + Zaregistrovat se + + + Odhlásit se + + + Znovu odeslat kód na vaše registrované zařízení SMS + + + Poslat znovu + + + Název úložiště + + + Toto úložiště nemá vzdálené úložiště. Vyplňte formulář pro jeho publikování na GitHubu. + + + Publikovat + + + nebo + + + Otevřete aplikaci pro dvoufaktorové ověřování na vašem zařízení a zobrazte svůj ověřovací kód. + + + Žádná úložiště + + + Název + + + Privátní úložiště + + + Místní cesta: + + + Licence + + + Další informace + + + Ignorování Gitu + + + Hledat úložiště + + + Některá nebo všechna úložiště se možná nenačetla. Zavřete dialogové okno a zkuste to znovu. + + + Při načítání úložišť došlo k chybě. + + + Adresa serveru GitHub Enterprise + + + Hostitel není dostupný nebo se nejedná o server GitHub Enterprise. Zkontrolujte adresu a zkuste to znovu. + + + Uživatelské jméno nebo e-mail + + + Heslo + + + Přihlásit se + + + Zkontrolujte prosím připojení k internetu a zkuste to znovu. + + + Nemáte GitHub Enterprise? + + + Nemáte účet? + + + Název (povinné) + + + Popis + + + Publikujte toto úložiště na GitHub a získáte výkonnou spolupráci, revizi kódu a správu kódu pro open source a privátní projekty. + + + Toto úložiště není na GitHubu. + + + Žádné úložiště + + + Žádné úložiště git jsme tady nenašli. Pokud chcete začít, otevřete projekt git nebo klikněte v projektu na Soubor -> Přidat do správy zdrojového kódu. + + + Vytvořit účet + + + Filtrovat větve + + + Publikovat na GitHub + + + Začínáme + + + Přihlásit se + + + Přihlásit se... + + + Místní větev je aktuální. + + + Změny ({0}) + + + Zobrazit změny + + + Porovnat soubor jako výchozí akce + + + Zobrazit soubor + + + Otevřít soubor jako výchozí akce + + + Přepnout na zobrazení seznamu + + + Přepnout na stromové zobrazení + + + aktualizoval(a) {0} + + + Zobrazit žádost o přijetí změn na GitHubu + + + Vítá vás GitHub pro Visual Studio. Podívejte se na naše [školení](show-training) a [dokumentaci](show-docs). + +[Příště už nezobrazovat](dont-show-again) + + + Aktualizováno + + + Zobrazit komentáře PR na okraji editoru + + + Experimentální funkce + + + Tyto funkce se můžou v budoucí verzi změnit. + + + Zobrazit změny v řešení + + + Otevřít soubor v řešení + + + Token + + + Pokračovat v revizi + + + Přidat revizi + + + Revidující + + + Přidat komentář revize + + + Přidat jeden komentář + + + Fork + + + Ladění + + + Povolit protokolování trasování + + + Rozšíření GitHub není k dispozici v Blendu. + + + Aktualizovat komentář + + + Zrušit + + + Čekající + + + Zahájit revizi + + + Pokud sem chcete přidat komentář, musíte zapsat a nasdílet změny. + + + Předchozí komentář + + + Další komentář + + + Zobrazit, rezervovat nebo vytvořit žádost o přijetí změn + + + Zpět + + + Fork úložiště + + + Fork úložiště + + + Aktualizovat pro místní úložiště + + + přejít na + + + Vytvořit žádost o přijetí změn + + + Přihlásit se ke GitHubu + + + Přijmout změny + + + Vložit + + + Synchronizovat + + + zapsal(a) + + + Odeslat revizi pro + + + Souhrn vaší revize + + + Jenom komentář + + + Schválit + + + Požádat o změny + + + Komentáře + + + Zastaralé komentáře + + + Vytvořit nový + + + Pověřená osoba + + + Autor + + + Přihlásit se pomocí prohlížeče + + + Otevřít + + + Filtrovat podle autora + + + Vybrat fork + + + Opravdu chcete odstranit tento komentář? + + + Odstranit komentář + + + Opakovat + + + Nejsou k dispozici žádné otevřené žádosti o přijetí změn. + + + Žádosti o přijetí změn vám umožňují informovat ostatní o změnách, které jste nasdíleli do úložiště na GitHubu. + + + Vašemu hledání neodpovídají žádné výsledky. + + + Pokud chcete začít, můžete + + + vytvořit žádost o přijetí změn + + + Otevřít úložiště v {0}? + + + Cílová adresa URL má jiného vlastníka než aktuální úložiště. + + + Není k dispozici žádné aktivní úložiště, do kterého by bylo možné přejít. + + + Pracovní soubor je jiný než soubor v {0}. Rezervujte si prosím odpovídající větev, žádost o přijetí změn nebo zápis. + + + Otevřete prosím úložiště {0} a zkuste to znovu. + + + Nelze otevřít z {0}. Momentálně jsou podporované jenom adresy URL, které odkazují na soubory v úložišti. + + + Ve schránce se nepodařilo najít adresu URL na GitHubu. + + + V aktuálním úložišti se nepodařilo najít cílovou adresu URL. Zkuste to znovu po načtení změn. + + + Adresář v cílové cestě není prázdný. + + + Úložiště už v tomto umístění existuje, ale nemá vzdálené úložiště s názvem origin. + + + Úložiště už v tomto umístění existuje, ale má vzdálené úložiště {0}. + + + Do tohoto umístění jste už klonovali. Kliknutím na Otevřít otevřete místní úložiště. + + + Pro úložiště nelze najít adresu URL na GitHubu. + + + Úložiště musí mít definované vzdálené úložiště s názvem origin, aby bylo možné vyhledat jejich adresu URL na GitHubu. + + + Přejmenujte prosím jedno z vašich existujících vzdálených úložišť na origin nebo přidejte nové vzdálené úložiště s názvem origin a načtěte je. Můžete to provést z příkazového řádku nebo kliknutím na tlačítko níže. + + + V úložišti se nepodařilo najít soubor odpovídající {0}. Proveďte prosím příkaz git fetch nebo rezervujte žádost o přijetí změn v cíli. + + + {0} – naposledy nasdíleno + + + Komentář + + + Zavřít žádost o přijetí změn + + + Zavřít problém + + + Zavřít a okomentovat + + + Znovu otevřít a okomentovat + + + Znovu otevřít problém + + + Znovu otevřít žádost o přijetí změn + + + Počet zápisů: {0} + + + přidal(a) nějaké zápisy + + + a další + + + Tato konverzace byla označena jako vyřešená. + + + Vaše úložiště + + + Úložiště spolupracovníka + + + Přidáno do úložišť + + + Vyhledejte nebo zadejte adresu URL. + + + + Procházet... + + + Klon + + + Přidat nebo změnit účty + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.de-DE.resx b/src/GitHub.Resources/Resources.de-DE.resx new file mode 100644 index 0000000000..42da2e0b7e --- /dev/null +++ b/src/GitHub.Resources/Resources.de-DE.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Wählen Sie einen enthaltenden Ordner für Ihr neues Repository aus. + + + Aus GitHub öffnen + + + Mit Github.com konnte keine Verbindung hergestellt werden. + + + GitHub-Gist erstellen + + + {0}-Repository erstellen + + + GistFromVisualStudio.cs + + + Geben Sie eine Enterprise-URL ein. + + + Geben Sie eine gültige Enterprise-URL ein. + + + Kein Enterprise-Server. Geben Sie eine Enterprise-URL ein. + + + (Kennwort vergessen?) + + + Stellen Sie sicher, dass Sie zur Anmeldung Ihr Kennwort und kein persönliches Zugriffstoken verwenden. + + + Überprüfen Sie Ihren Benutzernamen und das Kennwort, und versuchen Sie es dann noch mal. + + + Fehler bei der Anmeldung. + + + Verbindung mit GitHub herstellen + + + Geben Sie Ihr Kennwort ein. + + + Pull Request für Branch **{0}** wurde unter [{1}]({2}) erfolgreich erstellt. + + + Geben Sie einen Titel für den Pull Request ein. + + + Quell- und Zielbranch dürfen nicht identisch sein. + + + Der Quellpfad ist im Remoterepository nicht vorhanden, haben Sie ihn mithilfe von Push übertragen? + + + Geben Sie einen Repositorypfad ein. + + + Geben Sie einen gültigen Pfad ein. + + + Der Pfad enthält ungültige Zeichen. + + + Der Pfad ist zu lang. + + + Das Repository "{0}/{1}" ist bereits vorhanden. + + + Ändern Sie den Repositorynamen, oder wählen Sie ein anderes Konto aus, und versuchen Sie es noch mal. + + + Kontingent für private Repositorys überschritten. + + + Ein Repository mit dem gleichen Namen ist an diesem Speicherort bereits vorhanden. + + + Geben Sie einen Repositorynamen ein. + + + Der Repositoryname muss weniger als 100 Zeichen enthalten. + + + Wird als "{0}" erstellt. + + + Öffnen Sie die App zur zweistufigen Authentifizierung auf Ihrem Gerät, um Ihren Authentifizierungscode anzuzeigen. + + + Wir haben Ihnen eine SMS mit dem Authentifizierungscode gesendet. + + + Zweistufige Authentifizierung erforderlich + + + Geben Sie hier einen Authentifizierungscode für die Anmeldung ein. + + + Geben Sie Ihren Benutzernamen oder Ihre E-Mail-Adresse ein. + + + Der Benutzername oder die E-Mail-Adresse darf keine Leerzeichen enthalten. + + + Pull Requests + + + Pull Request + + + hinzufügen + + + Forken + + + [ungültig] + + + Vor dem Pushvorgang muss ein Pull erfolgen. + + + Es liegen keine Commits für einen Pullvorgang vor. + + + Es liegen keine Commits für einen Pushvorgang vor. + + + *Keine Beschreibung angegeben.* + + + "{0}" auschecken + + + Bei "{0}" auschecken + + + Per Pull aus {0}-Branch "{1}" übertragen + + + Per Push zu {0}-Branch "{1}" übertragen + + + Remoterepository + + + umbenennen + + + Das Quellrepository ist nicht mehr verfügbar. + + + Auschecken nicht möglich, weil für Ihr Arbeitsverzeichnis ausgecheckte Änderungen vorliegen. + + + {0} Untermodule synchronisieren + + + "Git.exe" wurde in PATH nicht gefunden. + +Installieren Sie Git für Windows: +https://git-scm.com/download/win + + + Genehmigt + + + Angeforderte Änderungen + + + Kommentiert + + + InBearbeitung + + + Drücken Sie die EINGABETASTE, um zum Editor zu navigieren. + + + Checken Sie den PR-Branch aus, bevor Sie zum Editor navigieren. + + + Drücken Sie die EINGABETASTE, um zum Editor zu navigieren. (PR-Branch muss ausgecheckt sein.) + + + Repository forken + + + Ursprung wechseln + + + Möchten Sie diesen Review abbrechen? Alle ausstehenden Kommentare gehen verloren. + + + Review abbrechen + + + Im Zielpfad ist eine Datei vorhanden. + + + Abmeldung erforderlich. + + + Vor {0:N0} Tag + + + Vor {0:N0} Tagen + + + Vor {0:N0} Stunde + + + Vor {0:N0} Stunden + + + jetzt + + + Vor {0:N0} Minute + + + Vor {0:N0} Minuten + + + Vor {0:N0} Monat + + + Vor {0:N0} Monaten + + + Vor {0:N0} Sekunde + + + Vor {0:N0} Sekunden + + + Vor {0:N0} Jahr + + + Vor {0:N0} Jahren + + + Ungültiger Authentifizierungscode. + + + Geben Sie den Code erneut ein, oder klicken Sie auf die Schaltfläche "Erneut senden", um einen neuen Authentifizierungscode zu erhalten. + + + Authentifizierungscode gesendet! + + + Wenn Sie keinen Authentifizierungscode erhalten, wenden Sie sich an support@github.com. + + + Durchsuchen + + + Mit Github.com konnte keine Verbindung hergestellt werden. + + + Verbindung mit dem Server nicht möglich. + + + Erstellen + + + Beschreibung (optional) + + + In Browser öffnen + + + Abbrechen + + + Gist wurde erstellt. + + + Fehler beim Erstellen von Gist. + + + durch + + + Datenschutz + + + Helfen Sie uns bei der Verbesserung unserer Produkte, indem Sie anonyme Nutzungsdaten senden. + + + Das Kopieren in die Zwischenablage war nicht möglich. Versuchen Sie es noch mal. + + + Der Link wurde in die Zwischenablage kopiert. + + + Das Repository wurde erfolgreich erstellt. + + + Privates Gist + + + Dateiname + + + Sie sind nicht bei {0} angemeldet, daher tritt bei bestimmten Git-Vorgängen möglicherweise ein Fehler auf. [Jetzt anmelden]({1}) + + + Wiki + + + Puls + + + Pfad + + + Probleme + + + Diagramme + + + In GitHub veröffentlichen + + + Leistungsstarke Funktionen für Zusammenarbeit, Code Review und Codeverwaltung für Open Source- und private Projekte. + + + Verbindung herstellen... + + + Klonen + + + Überprüfen + + + Zweistufige Authentifizierung + + + Registrieren + + + Abmelden + + + Code erneut an Ihr registriertes SMS-Gerät senden + + + Erneut senden + + + Repositoryname + + + Dieses Repository weist kein Remoterepository auf. Füllen Sie das Formular aus, um es in GitHub zu veröffentlichen. + + + Veröffentlichen + + + oder + + + Öffnen Sie die App zur zweistufigen Authentifizierung auf Ihrem Gerät, um Ihren Authentifizierungscode anzuzeigen. + + + Keine Repositorys. + + + Name + + + Privates Repository + + + Lokaler Pfad: + + + Lizenz + + + Weitere Informationen + + + Git ignorieren + + + Repositorys durchsuchen + + + Einige oder alle Repositorys wurden möglicherweise nicht geladen. Schließen Sie das Dialogfeld, und versuchen Sie es noch mal. + + + Fehler beim Laden von Repositorys. + + + GitHub Enterprise-Serveradresse + + + Der Host ist nicht verfügbar, oder es handelt sich nicht um einen GitHub Enterprise-Server. Überprüfen Sie die Adresse, und versuchen Sie es noch mal. + + + Benutzername oder E-Mail-Adresse + + + Kennwort + + + Anmelden + + + Prüfen Sie Ihre Internetverbindung, und versuchen Sie es noch mal. + + + Sie verfügen nicht über GitHub Enterprise? + + + Sie haben noch kein Konto? + + + Titel (erforderlich) + + + Beschreibung + + + Veröffentlichen Sie dieses Repository in GitHub, und erhalten Sie leistungsstarke Funktionen für Zusammenarbeit, Code Review und Codeverwaltung für Open Source- und private Projekte. + + + Dieses Repository befindet sich nicht auf GitHub. + + + Kein Repository + + + Hier wurde kein Git-Repository gefunden. Öffnen Sie ein Git-Projekt, oder klicken Sie in einem Projekt auf "Datei" > "Zur Quellcodeverwaltung hinzufügen", um den Vorgang zu starten. + + + Konto erstellen + + + Branches filtern + + + In GitHub veröffentlichen + + + Erste Schritte + + + Anmelden + + + Anmelden... + + + Lokaler Branch auf dem neuesten Stand + + + Änderungen ({0}) + + + Änderungen anzeigen + + + Datei vergleichen als Standardaktion + + + Datei anzeigen + + + Datei öffnen als Standardaktion + + + Zur Listenansicht wechseln + + + Zur Strukturansicht wechseln + + + "{0}" aktualisiert. + + + Pull Request in GitHub anzeigen + + + Willkommen bei GitHub für Visual Studio! Sehen Sie sich doch unser [Training](show-training) oder die [Dokumentation](show-docs) an. + +[Nicht erneut anzeigen](dont-show-again) + + + Aktualisiert + + + PR-Kommentare in Editor-Rand anzeigen + + + Experimentelle Features + + + Diese Features werden in einer zukünftigen Version möglicherweise geändert. + + + Änderungen in Projektmappe anzeigen + + + Datei in Projektmappe öffnen + + + Token + + + Review fortsetzen + + + Review hinzufügen + + + Reviewer + + + Reviewkommentar hinzufügen + + + Einzelnen Kommentar hinzufügen + + + Fork + + + Debugging + + + Ablaufprotokollierung aktivieren + + + Die GitHub-Erweiterung ist innerhalb von Blend nicht verfügbar. + + + Kommentar aktualisieren + + + Abbrechen + + + Ausstehend + + + Review starten + + + Sie müssen für Ihre Änderungen einen Commit und einen Pushvorgang ausführen, um hier einen Kommentar hinzuzufügen. + + + Vorheriger Kommentar + + + Nächster Kommentar + + + Pull Request anzeigen, auschecken oder erstellen + + + Zurück + + + Repository forken + + + Repository forken + + + Lokale Repositorys aktualisieren + + + zum Verweis auf + + + Pull Request erstellen + + + Bei GitHub anmelden + + + Pull + + + Push + + + Sync + + + schrieb + + + Review übermitteln für + + + Ihre Reviewzusammenfassung + + + Nur Kommentar + + + Genehmigen + + + Änderungen anfordern + + + Kommentare + + + Veraltete Kommentare + + + Neues Element erstellen + + + Zugewiesene Person + + + Autor + + + Mit Ihrem Browser anmelden + + + Öffnen + + + Nach Autor filtern + + + Fork auswählen + + + Möchten Sie diesen Kommentar wirklich löschen? + + + Kommentar löschen + + + Wiederholen + + + Es liegen keine offenen Pull Requests vor. + + + Mithilfe von Pull Requests können Sie andere Benutzer über Änderungen informieren, die Sie per Push an ein Repository in GitHub übertragen haben. + + + Mit Ihren Suchkriterien stimmen keine Ergebnisse überein. + + + Zum Einstieg können Sie folgende Aktionen durchführen: + + + Pull Request erstellen + + + Repository unter "{0}" öffnen? + + + Die Ziel-URL weist einen anderen Besitzer für das aktuelle Repository auf. + + + Es liegt kein aktives Repository für die Navigation vor. + + + Die Arbeitsdatei unterscheidet sich von der Datei unter "{0}". Checken Sie den entsprechenden Branch, Pull Request oder Commit aus. + + + Öffnen Sie das Repository "{0}", und versuchen Sie es noch mal. + + + Öffnen von "{0}" aus nicht möglich. Derzeit werden nur URLs unterstützt, die mit Repositorydateien verknüpft sind. + + + In der Zwischenablage wurde keine GitHub-URL gefunden. + + + Die Ziel-URL wurde im aktuellen Repository nicht gefunden. Versuchen Sie es nach einem Abrufvorgang (fetch) noch einmal. + + + Das Verzeichnis im Zielpfad ist nicht leer. + + + An diesem Speicherort ist bereits ein Repository vorhanden, das aber kein Remoterepository namens "origin" aufweist. + + + An diesem Speicherort ist bereits ein Repository vorhanden, das aber ein Remoterepository "{0}" aufweist. + + + Sie haben bereits einen Klonvorgang in diesen Speicherort durchgeführt. Klicken Sie auf "Öffnen", um das lokale Repository zu öffnen. + + + Die GitHub-URL für das Repository wurde nicht gefunden. + + + Für Repositorys muss ein Remoterepository namens "origin" definiert sein, damit die entsprechende GitHub-URL gefunden wird. + + + Benennen Sie eines der vorhandenen Remoterepositorys in "origin" um, oder fügen Sie ein neues Remoterepository namens "origin" hinzu, und führen Sie einen Abrufvorgang (fetch) durch. Dies kann über die Befehlszeile oder durch Klicken auf die untenstehende Schaltfläche erfolgen. + + + Die entsprechende Datei für "{0}" wurde im Repository nicht gefunden. Führen Sie "git fetch" aus, oder checken Sie den Ziel-Pull Request aus. + + + Letzte {0} mithilfe von Push übertragene Elemente + + + Kommentar + + + Pull Request schließen + + + Issue schließen + + + Schließen und kommentieren + + + Erneut öffnen und kommentieren + + + Issue öffnen + + + Pull Request erneut öffnen + + + {0} Commits + + + Einige Commits hinzugefügt + + + und weitere + + + Diese Konversation wurde als aufgelöst markiert. + + + Ihre Repositorys + + + Repositorys von Projektmitarbeitern + + + Beitrag zu Repositorys + + + URL suchen oder eingeben + + + + Durchsuchen... + + + Klonen + + + Konten hinzufügen/ändern + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.es-ES.resx b/src/GitHub.Resources/Resources.es-ES.resx new file mode 100644 index 0000000000..05cbd25f73 --- /dev/null +++ b/src/GitHub.Resources/Resources.es-ES.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Seleccione una carpeta contenedora para el nuevo repositorio. + + + Abrir desde GitHub + + + No se pudo conectar a github.com + + + Crear gist de GitHub + + + Crear un repositorio de {0} + + + GistFromVisualStudio.cs + + + Escriba una dirección URL de Enterprise + + + Escriba una dirección URL de Enterprise válida + + + No es un servidor Enterprise. Especifique una dirección URL de Enterprise. + + + (¿olvidó su contraseña?) + + + Asegúrese de usar la contraseña y no un token de acceso personal para iniciar sesión. + + + Compruebe el nombre de usuario y la contraseña y vuelva a intentarlo. + + + Error al iniciar sesión. + + + Conectarse a GitHub + + + Escriba su contraseña + + + La solicitud de incorporación de cambios para la rama **{0}** se creó correctamente a las [{1}]({2}) + + + Escriba un título para la solicitud de incorporación de cambios + + + La rama de origen y de destino no puede ser la misma. + + + La rama de origen no existe en la ubicación remota. ¿La ha insertado? + + + Escriba una ruta de acceso de repositorio. + + + Escriba una ruta de acceso válida. + + + La ruta de acceso contiene caracteres no válidos. + + + La ruta de acceso es demasiado larga. + + + El repositorio "{0}/{1}" ya existe. + + + Cambie el nombre del repositorio o seleccione otra cuenta y vuelva a intentarlo. + + + Se ha superado la cuota de repositorios privados. + + + Ya existe un repositorio con el mismo nombre en esta ubicación + + + Escriba un nombre de repositorio + + + El nombre del repositorio debe tener menos de 100 caracteres. + + + Se creará como {0} + + + Abra la aplicación de autenticación en dos fases en el dispositivo para ver el código de autenticación. + + + Le hemos enviado un mensaje mediante SMS con el código de autenticación. + + + Se requiere la autenticación en dos fases + + + Especifique aquí un código de autenticación de inicio de sesión + + + Escriba su nombre de usuario o dirección de correo electrónico. + + + El nombre de usuario o la dirección de correo electrónico no deben tener espacios. + + + Solicitudes de incorporación de cambios + + + Solicitud de incorporación de cambios + + + agregue + + + bifurcar + + + [no válido] + + + Debe incorporar los cambios antes de enviarlos + + + No hay commits para incorporar cambios + + + No hay commits para enviar cambios + + + *No se ha proporcionado ninguna descripción.* + + + Extraer {0} del repositorio + + + Extraer del repositorio en {0} + + + Incorporar cambios de {0}, rama {1} + + + Enviar cambios a {0}, rama {1} + + + repositorio remoto + + + cambiar nombre + + + El repositorio de origen ya no está disponible. + + + No se puede extraer del repositorio porque el directorio de trabajo tiene cambios pendientes de hacer commit. + + + Sincronizar {0} submódulos + + + No se encontró Git.exe en PATH. + +Instale GIT para Windows desde: +https://git-scm.com/download/win + + + Aprobado + + + Cambios solicitados + + + Comentado + + + En curso + + + Presione ENTRAR para ir al editor. + + + Extraiga del repositorio la rama de la solicitud de incorporación de cambios antes de navegar al Editor. + + + Presione ENTRAR para ir al editor (la rama de la solicitud de incorporación de cambios debe extraerse del repositorio). + + + Bifurcar repositorio + + + Cambiar origen + + + ¿Seguro que quiere cancelar esta revisión? Se perderán todos los comentarios pendientes. + + + Cancelar revisión + + + Existe un archivo en la ruta de acceso de destino. + + + Cierre de sesión necesario + + + hace {0:N0} día + + + hace {0:N0} días + + + hace {0:N0} hora + + + hace {0:N0} horas + + + ahora mismo + + + hace {0:N0} minuto + + + hace {0:N0} minutos + + + hace {0:N0} mes + + + hace {0:N0} meses + + + hace {0:N0} segundo + + + hace {0:N0} segundos + + + hace {0:N0} año + + + hace {0:N0} años + + + Código de autenticación no válido + + + Vuelva a escribir el código o haga clic en el botón de reenvío para obtener un nuevo código de autenticación. + + + El código de autenticación se ha enviado. + + + Si no recibe el código de autenticación, póngase en contacto con support@github.com. + + + Examinar + + + No se pudo conectar a github.com + + + No se pudo conectar al servidor. + + + Crear + + + Descripción (opcional) + + + Abrir en el explorador + + + Cancelar + + + Gist creada + + + No se pudo crear la característica gist + + + de + + + Privacidad + + + Envíenos datos de uso anónimos para ayudarnos a mejorar + + + No se pudo copiar en el Portapapeles. Vuelva a intentarlo. + + + Vínculo copiado al Portapapeles + + + El repositorio se ha creado correctamente. + + + Gist privada + + + Nombre de archivo + + + No ha iniciado sesión en {0}, por lo que algunas operaciones de GIT pueden generar errores. [Inicie sesión ahora]({1}) + + + Wiki + + + Pulso + + + Ruta de acceso + + + Problemas + + + Gráficos + + + Publicar en GitHub + + + Colaboración, revisión de código y administración de código eficaces para proyectos privados y de código abierto. + + + Conectar… + + + Clonar + + + Verificar + + + Autenticación en dos fases + + + Suscribirse + + + Cerrar sesión + + + Volver a enviar el código al dispositivo SMS registrado + + + Volver a enviar + + + Nombre del repositorio + + + Este repositorio no tiene un repositorio remoto. Rellene el formulario para publicarlo en GitHub. + + + Publicar + + + o + + + Abra la aplicación de autenticación en dos fases en el dispositivo para ver el código de autenticación. + + + No hay repositorios + + + Nombre + + + Repositorio privado + + + Ruta de acceso local: + + + Licencia + + + Más información + + + Omitir GIT + + + Buscar repositorios + + + Puede que no se hayan cargado algunos repositorios o ninguno de ellos. Cierre el cuadro de diálogo y vuelva a intentarlo. + + + Error al cargar los repositorios + + + Dirección del servidor de GitHub Enterprise + + + El host no está disponible o no es un servidor de GitHub Enterprise. Compruebe la dirección y vuelva a intentarlo. + + + Nombre de usuario o correo electrónico + + + Contraseña + + + Iniciar sesión + + + Compruebe la conexión a Internet y vuelva a intentarlo. + + + ¿No tiene GitHub Enterprise? + + + ¿No tiene ninguna cuenta? + + + Título (obligatorio) + + + Descripción + + + Publique este repositorio en GitHub para conseguir una colaboración, revisión de código y administración de código eficaces en los proyectos privados y de código abierto. + + + Este repositorio no está en GitHub + + + No se encontró ningún repositorio. + + + No se encontró ningún repositorio GIT aquí. Abra un proyecto GIT o haga clic en "Archivo -> Agregar al control de código fuente" en un proyecto para empezar. + + + Crear una cuenta + + + Filtrar ramas + + + Publicar en GitHub + + + Introducción + + + Iniciar sesión + + + Iniciar sesión... + + + Rama local actualizada + + + Cambios ({0}) + + + Ver cambios + + + Comparar archivo como acción predeterminada + + + Ver archivo + + + Abrir archivo como acción predeterminada + + + Cambiar a vista de lista + + + Cambiar a vista de árbol + + + actualizó {0} + + + Ver solicitud de incorporación de cambios en GitHub + + + Esto es GitHub para Visual Studio. ¿Por qué no echa un vistazo a nuestro [material de aprendizaje](show-training) o [documentación](show-docs)? + +[No volver a mostrar](dont-show-again) + + + Actualizado + + + Mostrar comentarios de solicitud de incorporación de cambios en margen de editor + + + Características experimentales + + + Estas características pueden cambiar en una versión futura. + + + Ver cambios en la solución + + + Abrir archivo en la solución + + + Token + + + Continuar la revisión + + + Agregar su revisión + + + Revisores + + + Agregar un comentario de revisión + + + Agregar un solo comentario + + + Bifurcación + + + Depuración + + + Habilitar el registro de seguimiento + + + La extensión de GitHub no está disponible en Blend + + + Actualizar el comentario + + + Cancelar + + + Pendiente + + + Iniciar una revisión + + + Debe hacer commit y enviar los cambios para agregar un comentario aquí. + + + Comentario anterior + + + Comentario siguiente + + + Ver, extraer del repositorio o crear una solicitud de incorporación de cambios + + + Atrás + + + Bifurcar repositorio + + + Bifurcar el repositorio + + + Actualizar el repositorio local + + + para que apunte a + + + Crear solicitud de incorporación de cambios + + + Iniciar sesión en GitHub + + + Extraer + + + Insertar + + + Sincronizar + + + escribió + + + Enviar la revisión para + + + Resumen de la revisión + + + Solo comentario + + + Aprobar + + + Solicitar cambios + + + Comentarios + + + Comentarios obsoletos + + + Crear nuevo + + + Persona asignada + + + Autor + + + Iniciar sesión con el explorador + + + Abrir + + + Filtrar por autor + + + Seleccionar una bifurcación + + + ¿Está seguro de que desea eliminar este comentario? + + + Eliminar comentario + + + Reintentar + + + No hay ninguna solicitud de incorporación de cambios abierta. + + + Las solicitudes de incorporación de cambios le permiten indicar a otros usuarios los cambios que ha enviado a un repositorio en GitHub. + + + No hay resultados que coincidan con su búsqueda. + + + Para empezar, puede + + + crear una solicitud de incorporación de cambios + + + ¿Abrir el repositorio en "{0}"? + + + La dirección URL de destino tiene un propietario distinto al del repositorio actual. + + + No hay ningún repositorio activo para navegar. + + + El archivo de trabajo es distinto al archivo que hay en "{0}". Extraiga del repositorio la rama, la solicitud de incorporación de cambios o el commit correspondiente. + + + Abra el repositorio "{0}" y vuelva a intentarlo + + + No se pudo abrir desde "{0}". Actualmente solo se admiten las direcciones URL que vinculan a archivos del repositorio. + + + No se encontró ninguna dirección URL de GitHub en el Portapapeles. + + + No se encontró la dirección URL de destino en el repositorio actual. Vuelva a intentarlo tras una recuperación de cambios. + + + El directorio en la ruta de acceso de destino no está vacío. + + + Ya existe un repositorio en esta ubicación, pero no tiene un repositorio remoto con el nombre "origen". + + + Ya existe un repositorio en esta ubicación, pero tiene un repositorio remoto de {0}. + + + Ya se ha realizado una clonación en esta ubicación. Haga clic en "Abrir" para abrir el repositorio local. + + + No se encuentra la dirección URL de GitHub para el repositorio + + + Los repositorios deben tener definido un repositorio remoto llamado "origen" para encontrar su dirección URL de GitHub. + + + Cambie el nombre de uno de los repositorios remotos actuales a "origen", o bien agregue uno nuevo llamado "origen" y recupere los cambios. Esta operación puede realizarse desde la línea de comandos o al hacer clic en el botón siguiente. + + + No se encontró el archivo correspondiente a "{0}" en el repositorio. Realice la operación "Búsqueda de GIT" o extraiga del repositorio la solicitud de incorporación de cambios de destino. + + + {0} con cambios enviados más recientemente + + + Comentario + + + Cerrar la solicitud de incorporación de cambios + + + Cerrar el problema + + + Cerrar y comentar + + + Volver a abrir y comentar + + + Volver a abrir el problema + + + Volver a abrir la solicitud de incorporación de cambios + + + {0} confirmaciones + + + se han agregado algunas confirmaciones + + + y otros + + + Esta conversación se ha marcado como resuelta + + + Los repositorios + + + Repositorios de colaborador + + + Contribución a los repositorios + + + Buscar o escribir una dirección URL + + + + Examinar... + + + Clonar + + + Agregar o cambiar cuentas + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.fr-FR.resx b/src/GitHub.Resources/Resources.fr-FR.resx new file mode 100644 index 0000000000..341162f61e --- /dev/null +++ b/src/GitHub.Resources/Resources.fr-FR.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Sélectionnez un dossier conteneur pour votre nouveau dépôt. + + + Ouvrir à partir de GitHub + + + Impossible de se connecter à github.com + + + Créer un Gist GitHub + + + Créer un dépôt {0} + + + GistFromVisualStudio.cs + + + Entrez une URL Enterprise + + + Entrez une URL Enterprise valide + + + Il ne s'agit pas d'un serveur Enterprise. Entrez une URL Enterprise + + + (vous avez oublié votre mot de passe ?) + + + Veillez à utiliser votre mot de passe et non un jeton d'accès personnel pour vous connecter. + + + Vérifiez votre nom d'utilisateur et votre mot de passe, puis recommencez + + + La connexion a échoué. + + + Se connecter à GitHub + + + Entrez votre mot de passe + + + Demande de tirage (pull request) pour la branche **{0}** créée à l'emplacement [{1}]({2}) + + + Entrez un titre pour la demande de tirage (pull request) + + + Les branches source et cible ne peuvent pas être les mêmes + + + La branche source n'existe pas à distance. L'avez-vous envoyée (push) ? + + + Entrez un chemin de dépôt + + + Entrez un chemin valide + + + Le chemin contient des caractères non valides + + + Chemin trop long + + + Le dépôt « {0}/{1} » existe déjà. + + + Changez le nom du dépôt ou sélectionnez un compte différent, puis réessayez. + + + Le quota de dépôts privés a été dépassé. + + + Un dépôt portant le même nom existe déjà à cet emplacement + + + Entrez un nom de dépôt + + + Le nom de dépôt ne doit pas dépasser 100 caractères + + + Sera créé en tant que {0} + + + Ouvrez l'application d'authentification à 2 facteurs sur votre appareil pour afficher votre code d'authentification. + + + Nous vous avons envoyé un SMS avec votre code d'authentification. + + + Authentification à 2 facteurs obligatoire + + + Entrer un code d'authentification de connexion ici + + + Entrez votre nom d'utilisateur ou votre adresse e-mail + + + Le nom d'utilisateur ou l'adresse e-mail ne doit pas comporter d'espaces + + + Requêtes de tirage + + + Demande de tirage + + + ajouter + + + dupliquer (fork) + + + [non valide] + + + Vous devez effectuer un tirage (pull) avant un envoi (push) + + + Aucun commit à tirer (pull) + + + Aucun commit à envoyer (push) + + + *Aucune description fournie.* + + + Extraire {0} + + + Extraire dans {0} + + + Tirer (pull) de {0} la branche {1} + + + Envoyer (push) à {0} la branche {1} + + + dépôt distant + + + renommer + + + Le dépôt source n'est plus disponible. + + + Extraction impossible car votre répertoire de travail contient des changements non commités. + + + Synchroniser {0} sous-modules + + + Git.exe est introuvable sur PATH. + +Installez Git pour Windows à partir de l'adresse suivante : +https://git-scm.com/download/win + + + Approuvé + + + Changements demandés + + + Commenté + + + En cours + + + Appuyez sur Entrée pour accéder à l'éditeur + + + Extraire la branche PR avant la navigation vers l'éditeur + + + Appuyez sur Entrée pour accéder à l'éditeur (la branche PR doit être extraite) + + + Dupliquer (fork) le dépôt + + + Changer de dépôt origin + + + Voulez-vous vraiment annuler cette revue ? Vous allez perdre tous vos commentaires en attente. + + + Annuler la revue + + + Un fichier existe dans le chemin de destination. + + + Déconnexion obligatoire + + + il y a {0:N0} jour + + + il y a {0:N0} jours + + + il y a {0:N0} heure + + + il y a {0:N0} heures + + + maintenant + + + il y a {0:N0} minute + + + il y a {0:N0} minutes + + + il y a {0:N0} mois + + + il y a {0:N0} mois + + + il y a {0:N0} seconde + + + il y a {0:N0} secondes + + + il y a {0:N0} an + + + il y a {0:N0} ans + + + Code d'authentification non valide + + + Essayez d'entrer à nouveau le code ou de cliquer sur le bouton de renvoi pour obtenir un nouveau code d'authentification. + + + Code d'authentification envoyé ! + + + Si vous ne recevez pas le code d'authentification, contactez support@github.com. + + + Parcourir + + + Impossible de se connecter à github.com + + + Impossible de se connecter au serveur. + + + Créer + + + Description (facultative) + + + Ouvrir dans un navigateur + + + Annuler + + + Gist créé + + + Échec de création de Gist + + + par + + + Confidentialité + + + Aidez-nous à nous améliorer en envoyant des données d'utilisation anonymes + + + Impossible de copier dans le Presse-papiers. Réessayez. + + + Lien copié dans le Presse-papiers + + + Le dépôt a été créé. + + + Gist privé + + + Nom de fichier + + + Vous n'êtes pas connecté à {0}, ce qui peut entraîner l'échec de certaines opérations git. [Connectez-vous maintenant]({1}) + + + Wiki + + + Pulsation + + + Chemin d'accès + + + Problèmes + + + Graphes + + + Publier sur GitHub + + + Fonctionnalités puissantes de collaboration, de revue et de gestion du code pour les projets open source et privés. + + + Connecter... + + + Cloner + + + Vérifier + + + Authentification à 2 facteurs + + + S'inscrire + + + Se déconnecter + + + Renvoyer le code à votre appareil SMS inscrit + + + Renvoyer + + + Nom du dépôt + + + Ce dépôt n'a pas de dépôt distant. Remplissez le formulaire pour le publier sur GitHub. + + + Publier + + + ou + + + Ouvrez l'application d'authentification à 2 facteurs sur votre appareil pour afficher votre code d'authentification. + + + Aucun dépôt + + + Nom + + + Référentiel privé + + + Chemin local : + + + Licence + + + En savoir plus + + + Git ignore + + + Rechercher dans les dépôts + + + Certains ou tous les dépôts n'ont peut-être pas été chargés. Fermez la boîte de dialogue et réessayez. + + + Une erreur s'est produite durant le chargement des dépôts + + + Adresse du serveur GitHub Enterprise + + + L'hôte n'est pas disponible ou n'est pas un serveur GitHub Enterprise. Vérifiez l'adresse, puis réessayez. + + + Nom d'utilisateur ou e-mail + + + Mot de passe + + + Se connecter + + + Vérifiez votre connexion Internet et réessayez. + + + Vous n'avez pas GitHub Enterprise ? + + + Vous n'avez pas de compte ? + + + Titre (obligatoire) + + + Description + + + Publiez ce dépôt sur GitHub et bénéficiez de fonctionnalités puissantes de collaboration, de revue et de gestion du code pour les projets open source et privés. + + + Ce dépôt n'est pas sur GitHub + + + Aucun dépôt + + + Nous n'avons trouvé aucun dépôt git ici. Ouvrez un projet git ou cliquez sur « Fichier -> Ajouter au contrôle de code source » dans un projet pour démarrer. + + + Créer un compte + + + Filtrer les branches + + + Publier sur GitHub + + + Prise en main + + + Se connecter + + + Se connecter... + + + Branche locale à jour + + + Modifications ({0}) + + + Afficher les modifications + + + Comparer le fichier en tant qu'action par défaut + + + Afficher le fichier + + + Ouvrir le fichier en tant qu'action par défaut + + + Basculer en vue Liste + + + Basculer en arborescence + + + {0} mis à jour + + + Afficher la demande de tirage (pull request) sur GitHub + + + Bienvenue dans GitHub pour Visual Studio ! Pensez à consulter nos [formations](show-formation) et notre [documentation](show-docs). + +[Ne plus afficher ce message](dont-show-again) + + + Mis à jour + + + Afficher les commentaires PR sur l'éditeur de marge + + + Fonctionnalités expérimentales + + + Ces fonctionnalités peuvent être amenées à changer dans une version ultérieure + + + Afficher les changements dans la solution + + + Ouvrir le fichier dans la solution + + + Jeton + + + Continuer votre revue + + + Ajouter votre revue + + + Réviseurs + + + Ajouter un commentaire de revue + + + Ajouter un commentaire unique + + + Dupliquer (fork) + + + Débogage + + + Activer la journalisation du suivi + + + L'extension GitHub n'est pas disponible dans Blend + + + Mettre à jour le commentaire + + + Annuler + + + En attente + + + Commencer une revue + + + Vous devez commiter et envoyer (push) vos changements pour ajouter un commentaire ici. + + + Commentaire précédent + + + Commentaire suivant + + + Afficher, extraire ou créer une demande de tirage (pull request) + + + Précédent + + + Dupliquer (fork) le dépôt + + + Dupliquer (fork) le dépôt + + + Mettre à jour votre dépôt local + + + pour pointer sur + + + Créer la demande de tirage (pull request) + + + Se connecter à GitHub + + + Tirer + + + Pousser + + + Synchroniser + + + a écrit + + + Envoyer votre revue pour + + + Résumé de votre revue + + + Commentaire uniquement + + + Approuver + + + Demander des changements + + + Commentaires + + + Commentaires obsolètes + + + Créer + + + Personne responsable + + + Auteur + + + Se connecter avec votre navigateur + + + Ouvrir + + + Filtrer par auteur + + + Sélectionner une duplication (fork) + + + Voulez-vous vraiment supprimer ce commentaire ? + + + Supprimer le commentaire + + + Réessayer + + + Il n'y a aucune demande de tirage (pull request) ouverte + + + Les demandes de tirage (pull request) vous permettent de signaler à d'autres personnes les changements que vous avez envoyés (push) à un dépôt sur GitHub + + + Aucun résultat ne correspond à votre recherche. + + + Pour commencer, vous pouvez + + + créer une demande de tirage (pull request) + + + Ouvrir le dépôt à l'emplacement « {0} » ? + + + L'URL cible a un propriétaire différent du dépôt actuel. + + + Aucun dépôt actif n'est accessible à la navigation + + + Le fichier de travail est différent du fichier à l'emplacement « {0} ». Extrayez la branche, la demande de tirage (pull request) ou le commit correspondant. + + + Ouvrez le dépôt « {0} », puis réessayez + + + Ouverture impossible à partir de « {0} ». Seules les URL pointant vers des fichiers de dépôt sont actuellement prises en charge. + + + URL GitHub introuvable dans le Presse-papiers + + + L'URL cible est introuvable dans le dépôt actuel. Réessayez après une récupération (fetch). + + + Le répertoire du chemin de destination n'est pas vide. + + + Un dépôt existe déjà à cet emplacement, mais il n'a pas de dépôt distant nommé « origin ». + + + Un dépôt existe déjà à cet emplacement, mais il a un dépôt distant {0}. + + + Vous avez déjà cloné à cet emplacement. Cliquez sur Ouvrir pour ouvrir le dépôt local. + + + URL GitHub du dépôt introuvable + + + Les dépôts doivent avoir un dépôt distant appelé « origin » pour localiser leur URL GitHub. + + + Renommez l'un de vos dépôts distants existants « origin » ou ajoutez-en un nouveau nommé « origin » et effectuez une récupération (fetch). Pour cela, utilisez la ligne de commande ou cliquez sur le bouton ci-dessous. + + + Le fichier correspondant à « {0} » est introuvable dans le dépôt. Effectuez un « git fetch » ou extrayez la demande de tirage (pull request) cible. + + + {0} envoyé (push) le plus récemment + + + Commentaire + + + Fermer la demande de tirage + + + Fermer le problème + + + Fermer et commenter + + + Rouvrir et commenter + + + Rouvrir le problème + + + Rouvrir la demande de tirage + + + {0} validations + + + commits ajoutés + + + et autres + + + Cette conversation a été marquée comme résolue + + + Vos dépôts + + + Dépôts de collaborateur + + + A contribué aux dépôts + + + Rechercher ou entrer une URL + + + + Parcourir... + + + Cloner + + + Ajouter/Changer des comptes + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.it-IT.resx b/src/GitHub.Resources/Resources.it-IT.resx new file mode 100644 index 0000000000..2d109fe4d3 --- /dev/null +++ b/src/GitHub.Resources/Resources.it-IT.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Selezionare una cartella contenitore per il nuovo repository. + + + Apri da GitHub + + + Non è stato possibile connettersi a github.com + + + Crea un gist GitHub + + + Crea un repository {0} + + + GistFromVisualStudio.cs + + + Immettere un URL di Enterprise + + + Immettere un URL valido di Enterprise + + + Non è un server Enterprise. Immettere un URL di Enterprise + + + (password dimenticata?) + + + Assicurarsi di usare la password e non un token di accesso personale per eseguire l'accesso. + + + Verificare nome utente e password, quindi riprovare + + + L'accesso non è riuscito. + + + Connetti a GitHub + + + Immettere la password + + + La richiesta pull per il ramo **{0}** è stata creata in [{1}]({2}) + + + Immettere un titolo per la richiesta pull + + + I rami di origine e di destinazione non possono essere uguali + + + Il ramo di origine non esiste in remoto. Ne è stato eseguito il push? + + + Immettere un percorso per il repository + + + Immettere un percorso valido + + + Il percorso contiene caratteri non validi + + + Il percorso è troppo lungo + + + Il repository '{0}/{1}' esiste già. + + + Modificare il nome del repository oppure selezionare un account diverso e riprovare. + + + È stata superata la quota di repository privati. + + + In questo percorso esiste già un repository con lo stesso nome + + + Immettere un nome per il repository + + + Il nome del repository non deve superare i 100 caratteri + + + Verrà creato con il nome {0} + + + Aprire l'app di autenticazione a due fattori nel dispositivo per visualizzare il codice di autenticazione. + + + È stato inviato un SMS con il codice di autenticazione. + + + Autenticazione a due fattori obbligatoria + + + Immettere qui il codice di autenticazione per l'accesso + + + Immettere il nome utente o l'indirizzo di posta elettronica + + + Il nome utente o l'indirizzo di posta elettronica non deve contenere spazi + + + Richieste pull + + + Richiesta pull + + + aggiungere + + + fork + + + [non valido] + + + È necessario eseguire il pull prima di eseguire il push + + + Non sono presenti commit di cui eseguire il pull + + + Non sono presenti commit di cui eseguire il push + + + *Non è stata specificata alcuna descrizione.* + + + Esegui checkout di {0} + + + Esegui checkout in {0} + + + Eseguire pull dal ramo {1} di {0} + + + Eseguire push dal ramo {1} di {0} + + + repository remoto + + + rinomina + + + Il repository di origine non è più disponibile. + + + Non è possibile eseguire il checkout perché la directory di lavoro contiene modifiche non sottoposte a commit. + + + Sincronizza i moduli secondari di {0} + + + Non è stato possibile trovare Git.exe in PATH. + +Installare GIT per Windows da: +https://git-scm.com/download/win + + + Approvate + + + Modifiche richieste + + + Commento aggiunto + + + In corso + + + Premere INVIO per spostarsi nell'editor + + + Eseguire il checkout del ramo della richiesta pull prima di passare all'editor + + + Premere INVIO per spostarsi nell'editor (è necessario aver eseguito il checkout del ramo della richiesta pull) + + + Crea una copia del repository tramite fork + + + Cambia origine + + + Annullare questa revisione? Tutti i commenti in sospeso andranno persi. + + + Annulla revisione + + + Nel percorso di destinazione è presente un file. + + + È necessario disconnettersi + + + {0:N0} giorno fa + + + {0:N0} giorni fa + + + {0:N0} ora fa + + + {0:N0} ore fa + + + adesso + + + {0:N0} minuto fa + + + {0:N0} minuti fa + + + {0:N0} mese fa + + + {0:N0} mesi fa + + + {0:N0} secondo fa + + + {0:N0} secondi fa + + + {0:N0} anno fa + + + {0:N0} anni fa + + + Codice di autenticazione non valido + + + Provare a immettere di nuovo il codice o a fare clic sul pulsante Invia di nuovo per ottenere un nuovo codice di autenticazione. + + + Il codice di autenticazione è stato inviato. + + + Se non si riceve il codice di autenticazione, contattare support@github.com. + + + Sfoglia + + + Non è stato possibile connettersi a github.com + + + Non è stato possibile connettersi al server. + + + Crea + + + Descrizione (facoltativa) + + + Apri nel browser + + + Annulla + + + Il gist è stato creato + + + Non è stato possibile creare il gist + + + da + + + Privacy + + + È possibile inviare dati di utilizzo anonimi per contribuire al miglioramento del prodotto + + + Non è stato possibile copiare negli Appunti. Riprovare. + + + Collegamento copiato negli Appunti + + + Il repository è stato creato. + + + Gist privato + + + Nome file + + + Non è stato eseguito l'accesso a {0}, di conseguenza alcune operazioni GIT potrebbero non riuscire. [Accedi adesso]({1}) + + + Wiki + + + A impulsi + + + Percorso + + + Problemi + + + Grafici + + + Pubblica in GitHub + + + Potenti funzionalità di collaborazione, revisione del codice e gestione del codice per progetti privati e open source. + + + Connetti… + + + Clona + + + Verifica + + + Autenticazione a due fattori + + + Iscrizione + + + Disconnetti + + + Invia di nuovo il codice al dispositivo SMS registrato + + + Rinvia + + + Nome repository + + + In questo repository non è presente un repository remoto. Compilare il modulo per pubblicarlo in GitHub. + + + Pubblica + + + oppure + + + Aprire l'app di autenticazione a due fattori nel dispositivo per visualizzare il codice di autenticazione. + + + Non esiste alcun repository + + + Nome + + + Repository privato + + + Percorso locale: + + + Licenza + + + Altre informazioni + + + Elenco gitignore + + + Cerca nei repository + + + È possibile che alcuni o tutti i repository non siano stati caricati. Chiudere la finestra di dialogo e riprovare. + + + Si è verificato un errore durante il caricamento dei repository + + + Indirizzo del server GitHub Enterprise + + + L'host non è disponibile oppure non è un server GitHub Enterprise. Controllare l'indirizzo e riprovare. + + + Nome utente o indirizzo di posta elettronica + + + Password + + + Accedi + + + Controllare la connessione Internet e riprovare. + + + GitHub Enterprise non è disponibile? + + + Non si dispone di un account? + + + Titolo (obbligatorio) + + + Descrizione + + + Pubblicando questo repository in GitHub è possibile accedere a potenti funzionalità di collaborazione, revisione del codice e gestione del codice per progetti privati e open source. + + + Questo repository non è presente in GitHub + + + Non è stato trovato alcun repository + + + Non è stato possibile trovare alcun repository GIT qui. Per iniziare, aprire un progetto git o fare clic su "File -> Aggiungi al controllo del codice sorgente" in un progetto. + + + Crea un account + + + Filtra rami + + + Pubblica in GitHub + + + Guida introduttiva + + + Accedi + + + Accedi... + + + Il ramo locale è aggiornato + + + Modifiche ({0}) + + + Visualizza modifiche + + + Confronta file come azione predefinita + + + Visualizza file + + + Apri file come azione predefinita + + + Passa a visualizzazione elenco + + + Passa a visualizzazione albero + + + {0} aggiornato + + + Visualizza richiesta pull in GitHub + + + Benvenuti a GitHub per Visual Studio. Per iniziare, provare a seguire il [training](show-training) o a consultare la [documentazione](show-docs). + +[Non visualizzare più](dont-show-again) + + + Aggiornato + + + Mostra i commenti alla richiesta pull a margine dell'editor + + + Funzionalità sperimentali + + + Queste funzionalità potrebbero cambiare in una versione futura + + + Visualizza modifiche nella soluzione + + + Apri file nella soluzione + + + Token + + + Continua la revisione + + + Aggiungi la revisione + + + Revisori + + + Aggiungi commento sulla revisione + + + Aggiungi un singolo commento + + + Crea una copia tramite fork + + + Debug + + + Abilita registrazione traccia + + + L'estensione GitHub non è disponibile in Blend + + + Aggiorna il commento + + + Annulla + + + In sospeso + + + Avvia una revisione + + + È necessario eseguire il commit e il push delle modifiche per aggiungere un commento qui. + + + Commento precedente + + + Commento successivo + + + Visualizza, esegui checkout o crea una richiesta pull + + + Indietro + + + Crea una copia del repository tramite fork + + + Crea una copia del repository tramite fork + + + Aggiorna il repository locale + + + in modo che punti a + + + Crea richiesta pull + + + Accedi a GitHub + + + Pull + + + Effettua il push + + + Sincronizzazione + + + scritto + + + Invia la revisione per + + + Riepilogo delle revisioni + + + Commenta solo + + + Approva + + + Modifiche della richiesta + + + Commenti + + + Commenti obsoleti + + + Crea nuova + + + Assegnatario + + + Autore + + + Accedi con il browser + + + Apri + + + Filtra per autore + + + Seleziona fork + + + Eliminare questo commento? + + + Elimina commento + + + Riprova + + + Non sono presenti richieste pull aperte + + + Le richieste pull consentono di informare altri utenti sulle modifiche di cui è stato eseguito il push in un repository in GitHub + + + Non sono stati trovati risultati corrispondenti alla ricerca. + + + Per iniziare, è possibile + + + creare una richiesta pull + + + Aprire il repository in '{0}'? + + + Il proprietario dell'URL di destinazione è diverso da quello del repository corrente. + + + Non è presente alcun repository attivo da esplorare + + + Il file di lavoro è diverso dal file in '{0}'. Eseguire il checkout del ramo, della richiesta pull o del commit corrispondente. + + + Aprire il repository '{0}' e riprovare + + + Non è stato possibile aprire da '{0}'. Al momento sono supportati solo gli URL collegati a file di repository. + + + Non è stato trovato alcun URL GitHub negli Appunti + + + Non è stato trovato alcun URL di destinazione nel repository corrente. Riprovare dopo aver eseguito il comando fetch. + + + La directory nel percorso di destinazione non è vuota. + + + In questo percorso esiste già un repository che però non include un repository remoto denominato "origin". + + + In questo percorso esiste già un repository che però contiene un repository remoto di {0}. + + + È già stata eseguita la clonazione in questo percorso. Fare clic su 'Apri' per aprire il repository locale. + + + Non è possibile trovare l'URL GitHub per il repository + + + Nei repository è necessario definire un repository remoto denominato "origin" per consentire l'individuazione dell'URL GitHub. + + + Rinominare uno dei repository remoti esistenti in 'origin' oppure aggiungere un nuovo repository remoto denominato 'origin' ed eseguire il comando fetch. È possibile eseguire questa operazione dalla riga di comando oppure facendo clic sul pulsante in basso. + + + Non è stato possibile trovare il file corrispondente a '{0}' nel repository. Eseguire 'git fetch' oppure il checkout della richiesta pull di destinazione. + + + {0} di cui è stato eseguito il push più di recente + + + Commento + + + Chiudi la richiesta pull + + + Chiudi il problema + + + Chiudi e aggiungi commento + + + Riapri e aggiungi commento + + + Riapri il problema + + + Riapri la richiesta pull + + + {0} commit + + + ha aggiunto alcuni commit + + + e altri + + + Questa conversazione è stata contrassegnata come risolta + + + Repository personali + + + Repository dei collaboratori + + + Aggiunto come contributo ai repository + + + Cercare o immettere un URL + + + + Sfoglia... + + + Clona + + + Aggiungi/Modifica account + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.ja-JP.resx b/src/GitHub.Resources/Resources.ja-JP.resx new file mode 100644 index 0000000000..7ed86f39cf --- /dev/null +++ b/src/GitHub.Resources/Resources.ja-JP.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 新しいリポジトリを含んでいるフォルダーを選択します。 + + + GitHub から開く + + + Github.com に接続できませんでした + + + GitHub Gist の作成 + + + {0} リポジトリの作成 + + + GistFromVisualStudio.cs + + + Enterprise URL を入力してください + + + 有効な Enterprise URL を入力してください + + + Enterprise サーバーではありません。Enterprise URL を入力してください + + + (パスワードを忘れた場合) + + + サインインするには、個人用アクセス トークンではなく、パスワードを使用してください。 + + + ユーザー名とパスワードを確認してから、もう一度お試しください + + + サインインできませんでした。 + + + GitHub に接続 + + + パスワードを入力してください + + + ブランチの Pull Request **{0}** が [{1}]({2}) に正常に作成されました + + + Pull Request のタイトルを入力してください + + + ソース ブランチとターゲット ブランチを同じにすることはできません + + + ソース ブランチがリモートに存在しません。ブランチをプッシュしましたか? + + + リポジトリのパスを入力してください + + + 有効なパスを入力してください + + + パスに無効な文字が含まれています + + + パスが長すぎます + + + リポジトリ '{0}/{1}' は既に存在します。 + + + リポジトリ名を変更するか、別のアカウントを選択してから、もう一度お試しください。 + + + プライベート リポジトリのクォータを超えました。 + + + 同じ名前のリポジトリがこの場所に既に存在します + + + リポジトリ名を入力してください + + + リポジトリ名は 100 文字未満でなければなりません + + + {0} として作成されます + + + デバイスで 2 要素認証アプリを開き、認証コードを確認してださい。 + + + SMS メッセージで認証コードを送信しました。 + + + 2 要素認証が必要です + + + サインインの認証コードをここに入力 + + + ユーザー名または電子メール アドレスを入力してください + + + ユーザー名または電子メール アドレスにスペースを含めることはできません + + + Pull Request + + + Pull Request + + + 追加 + + + フォーク + + + [無効] + + + プッシュする前にプルする必要があります + + + プルするコミットがありません + + + プッシュするコミットがありません + + + *説明が指定されていません。* + + + {0} のチェックアウト + + + {0} へのチェックアウト + + + {0} ブランチ {1} からプルします + + + {0} ブランチ {1} にプッシュします + + + リモート + + + 名前の変更 + + + ソース リポジトリは使用できなくなりました。 + + + 作業ディレクトリにコミットされていない変更があるため、チェックアウトできません。 + + + {0} サブモジュールの同期 + + + PATH 上に Git.exe が見つかりませんでした。 + +Git for Windows を次の場所からインストールしてください。 +https://git-scm.com/download/win + + + 承認済み + + + 変更が要求されました + + + コメントされました + + + 処理中 + + + エディターに移動するには Enter キーを押します + + + エディターに移動する前に PR ブランチをチェックアウトしてください + + + エディターに移動するには Enter キーを押します (PR ブランチをチェックアウトする必要があります) + + + リポジトリのフォーク + + + Origin の切り替え + + + このレビューをキャンセルしますか? 保留中のすべてのコメントが失われます。 + + + レビューのキャンセル + + + 宛先パスにファイルが存在します。 + + + ログアウトが必要です + + + {0:N0} 日前 + + + {0:N0} 日前 + + + {0:N0} 時間前 + + + {0:N0} 時間前 + + + 今すぐ + + + {0:N0} 分前 + + + {0:N0} 分前 + + + {0:N0} か月前 + + + {0:N0} か月前 + + + {0:N0} 秒前 + + + {0:N0} 秒前 + + + {0:N0} 年前 + + + {0:N0} 年前 + + + 認証コードが無効です + + + コードをもう一度入力してみてください。または、再送信ボタンをクリックして新しい認証コードを取得します。 + + + 認証コードを送信しました。 + + + 認証コードを受け取れない場合は、support@github.com にお問い合わせください。 + + + 参照 + + + Github.com に接続できませんでした + + + サーバーに接続できませんでした。 + + + 作成 + + + 説明 (オプション) + + + ブラウザーで開く + + + キャンセル + + + gist が作成されました + + + gist を作成できませんでした + + + 作成者 + + + プライバシー + + + 機能向上のために匿名の利用状況データを送信する + + + クリップボードにコピーできませんでした。もう一度お試しください。 + + + リンクがクリップボードにコピーされました + + + リポジトリが正常に作成されました。 + + + プライベート gist + + + ファイル名 + + + {0} にサインインしていないため、特定の Git 操作を実行できない可能性があります。[今すぐサインインする]({1}) + + + Wiki + + + パルス + + + パス + + + 懸案事項 + + + グラフ + + + GitHub に発行 + + + オープン ソースとプライベート プロジェクトの強力なコラボレーション、コード レビュー、コード管理が提供されます。 + + + 接続… + + + 複製 + + + 確認 + + + 2 要素認証 + + + サインアップ + + + サインアウト + + + 登録された SMS デバイスにコードを再送信します + + + 再送信 + + + リポジトリ名 + + + このリポジトリにはリモートがありません。このリポジトリを GitHub に発行するには、フォームに入力します。 + + + 公開 + + + または + + + デバイスで 2 要素認証アプリを開き、認証コードを確認してださい。 + + + リポジトリがありません + + + 名前 + + + プライベート リポジトリ + + + ローカル パス: + + + ライセンス + + + 詳細情報 + + + Git 無視 + + + リポジトリの検索 + + + 一部またはすべてのリポジトリが読み込まれていない可能性があります。ダイアログを閉じて、もう一度お試しください。 + + + リポジトリの読み込み中にエラーが発生しました + + + GitHub Enterprise サーバーのアドレス + + + ホストが使用できないか、GitHub Enterprise サーバーではありません。アドレスを確認して、もう一度お試しください。 + + + ユーザー名または電子メール + + + パスワード + + + サインイン + + + インターネット接続を確認して、もう一度お試しください。 + + + GitHub Enterprise をお持ちでない場合 + + + アカウントをお持ちでない場合 + + + タイトル (必須) + + + 説明 + + + このリポジトリを GitHub に発行すると、オープン ソースとプライベート プロジェクトの強力なコラボレーション、コード レビュー、コード管理が提供されます。 + + + このリポジトリは GitHub 上にありません + + + リポジトリが見つかりません + + + この場所に Git リポジトリが見つかりませんでした。Git プロジェクトを開くか、プロジェクトで [ファイル] -> [ソース管理に追加] をクリックして作業を開始してください。 + + + アカウントを作成する + + + ブランチのフィルター処理 + + + GitHub に発行 + + + はじめに + + + サインイン + + + サインイン... + + + 最新のローカル ブランチ + + + 変更 ({0}) + + + 変更の表示 + + + 既定のアクションとしてファイルを比較する + + + ファイルの表示 + + + 既定のアクションとしてファイルを開く + + + リスト ビューに切り替え + + + ツリー ビューに切り替え + + + {0} が更新されました + + + GitHub 上で Pull Request を表示 + + + GitHub for Visual Studio へようこそ! [トレーニング](show-training) または [ドキュメント](show-docs) をご覧ください。 + +[今後このメッセージを表示しない](dont-show-again) + + + 更新 + + + エディターの余白に PR コメントを表示する + + + 試験的な機能 + + + これらの機能は今後のバージョンで変更される可能性があります + + + ソリューション内の変更の表示 + + + ソリューション内のファイルを開く + + + トークン + + + レビューを続行 + + + レビューの追加 + + + レビュー担当者 + + + レビュー コメントの追加 + + + 単一コメントの追加 + + + フォーク + + + デバッグ + + + トレース ログを有効にする + + + Blend 内では GitHub 拡張機能を使用できません + + + コメントの更新 + + + キャンセル + + + 保留中 + + + レビューの開始 + + + ここにコメントを追加するには、変更をコミットしてプッシュする必要があります。 + + + 前のコメント + + + 次のコメント + + + Pull Request の表示、チェックアウト、または作成 + + + 戻る + + + リポジトリのフォーク + + + リポジトリをフォークします + + + ローカル リポジトリの次のものを更新します + + + ポイントする + + + pull request の作成 + + + GitHub にサインイン + + + プル + + + プッシュ + + + 同期 + + + 書き込みました + + + 次に対するレビュー送信をします: + + + レビューの概要 + + + コメントのみ + + + 承認 + + + 変更の要求 + + + コメント + + + 期限切れのコメント + + + 新規作成 + + + 担当者 + + + 作成者 + + + ブラウザーでサインインします + + + 開く + + + 作成者でフィルター処理 + + + フォークの選択 + + + このコメントを削除してもよろしいですか? + + + コメントの削除 + + + 再試行 + + + 開いている pull request はありません + + + pull request を使用すると、GitHub 上のリポジトリにプッシュした変更が他のユーザーに知らされます + + + 検索に一致する結果がありませんでした。 + + + 作業を開始するには、次のようにします。 + + + pull request を作成します + + + '{0}' にあるリポジトリを開きますか? + + + ターゲット URL には現在のリポジトリに対する別の所有者がいます。 + + + 移動するアクティブなリポジトリがありません + + + 作業ファイルは、'{0}' にあるファイルとは別のファイルです。対応するブランチ、pull request、またはコミットをチェックアウトしてください。 + + + リポジトリ '{0}' を開き、もう一度お試しください + + + '{0}' から開くことができませんでした。現在、リポジトリ ファイルにリンクする URL のみがサポートされています。 + + + クリップボード内に GitHub URL が見つかりませんでした + + + 現在のリポジトリにターゲット URL が見つかりませんでした。フェッチを実行した後、もう一度お試しください。 + + + ターゲット パスのディレクトリが空ではありません。 + + + この場所にリポジトリが既に存在しますが、そのリポジトリには "origin" という名前のリモートがありません。 + + + この場所にリポジトリが既に存在しますが、そのリポジトリには {0} のリモートがあります。 + + + この場所に対する複製は既に実行済みです。[開く] をクリックして、ローカル リポジトリを開いてください。 + + + リポジトリの GitHub URL が見つかりません + + + リポジトリの GitHub URL を検索するには、"origin" という名前のリモートがそのリポジトリで定義されている必要があります。 + + + 既存のリモートのいずれかの名前を 'origin' に変更するか、'origin' という名前の新しいリモートを追加してください。これを行うには、コマンド ラインを使用するか、下のボタンをクリックします。 + + + '{0}' に対応するファイルがリポジトリ内に見つかりませんでした。'git fetch' を実行するか、ターゲット pull request をチェックアウトしてください。 + + + {0} が最近プッシュされました + + + コメント + + + pull request を閉じる + + + 問題を閉じる + + + 閉じてコメントする + + + 再度開いてコメントする + + + 問題を再度開く + + + pull request を再度開く + + + {0} 個のコミット + + + コミットがいくつか追加されました + + + その他 + + + この会話は解決済みとしてマークされました + + + ご使用のリポジトリ + + + コラボレーターのリポジトリ + + + 参加先のリポジトリ + + + URL を検索または入力します + + + + 参照... + + + 複製 + + + アカウントの追加または変更 + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.ko-KR.resx b/src/GitHub.Resources/Resources.ko-KR.resx new file mode 100644 index 0000000000..c72155ba8f --- /dev/null +++ b/src/GitHub.Resources/Resources.ko-KR.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 새 리포지토리의 포함 폴더를 선택합니다. + + + GitHub에서 열기 + + + Github.com에 연결할 수 없습니다. + + + GitHub Gist 만들기 + + + {0} 리포지토리 만들기 + + + GistFromVisualStudio.cs + + + Enterprise URL을 입력하세요. + + + 유효한 Enterprise URL을 입력하세요. + + + Enterprise 서버가 아닙니다. Enterprise URL을 입력하세요. + + + (암호를 잊으셨습니까?) + + + 로그인하는 데 개인용 액세스 토큰이 아닌 암호를 사용해야 합니다. + + + 사용자 이름과 암호를 확인하고 다시 시도하세요. + + + 로그인에 실패했습니다. + + + GitHub에 연결 + + + 암호를 입력하세요. + + + 분기 **{0}**에 대한 끌어오기 요청이 [{1}]({2})에 생성됨 + + + 끌어오기 요청의 제목을 입력하세요. + + + 소스 및 대상 분기는 같을 수 없습니다. + + + 소스 분기가 원격으로 존재하지 않습니다. 소스 분기를 푸시하셨습니까? + + + 리포지토리 경로를 입력하세요. + + + 유효한 경로를 입력하세요. + + + 경로에 잘못된 문자가 들어 있습니다. + + + 경로가 너무 김 + + + 리포지토리 '{0}/{1}'이(가) 이미 있습니다. + + + 리포지토리 이름을 변경하거나 다른 계정을 선택하고 다시 시도하세요. + + + 비공개 리포지토리 할당량을 초과했습니다. + + + 같은 이름을 가진 리포지토리가 이 위치에 이미 있습니다. + + + 리포지토리 이름을 입력하세요. + + + 리포지토리 이름은 100자 미만이어야 합니다. + + + {0}(으)로 만들어집니다. + + + 디바이스에서 2단계 인증 앱을 열어 인증 코드를 봅니다. + + + SMS 통해 인증 코드가 포함된 메시지를 보냈습니다. + + + 2단계 인증 필요 + + + 여기에 로그인 인증 코드 입력 + + + 사용자 이름 또는 전자 메일 주소를 입력하세요. + + + 사용자 이름 또는 전자 메일 주소에는 공백이 없어야 합니다. + + + 끌어오기 요청 + + + 끌어오기 요청 + + + 추가 + + + 포크 + + + [잘못됨] + + + 푸시하려면 먼저 풀해야 합니다. + + + 풀할 커밋 없음 + + + 푸시할 커밋 없음 + + + *제공된 설명이 없습니다.* + + + {0} 체크 아웃 + + + {0}(으)로 체크 아웃 + + + {0} 분기 {1}에서 풀 + + + {0} 분기 {1}에 푸시 + + + 원격 + + + 이름 바꾸기 + + + 소스 리포지토리를 더 이상 사용할 수 없습니다. + + + 작업 디렉터리에 커밋되지 않은 변경 내용이 있으므로 체크 아웃할 수 없습니다. + + + {0} 하위 모듈 동기화 + + + PATH에서 Git.exe를 찾을 수 없습니다. + +다음 위치에서 Git for Windows를 설치하세요. +https://git-scm.com/download/win + + + 승인됨 + + + 요청된 변경 내용 + + + 주석 처리됨 + + + 진행 중 + + + <Enter> 키를 눌러 편집기로 이동 + + + 편집기로 이동하기 전에 PR 분기 체크 아웃 + + + <Enter> 키를 눌러 편집기로 이동(PR 분기를 체크 아웃해야 함) + + + 리포지토리 포크 + + + 원점 전환 + + + 이 검토를 취소하시겠습니까? 보류 중인 모든 주석이 손실됩니다. + + + 검토 취소 + + + 대상 경로에 파일이 있습니다. + + + 로그아웃 필요 + + + {0:N0}일 전 + + + {0:N0}일 전 + + + {0:N0}시간 전 + + + {0:N0}시간 전 + + + 방금 + + + {0:N0}분 전 + + + {0:N0}분 전 + + + {0:N0}개월 전 + + + {0:N0}개월 전 + + + {0:N0}초 전 + + + {0:N0}초 전 + + + {0:N0}년 전 + + + {0:N0}년 전 + + + 잘못된 인증 코드 + + + 코드를 다시 입력하거나, [다시 보내기] 단추를 클릭하여 새 인증 코드를 가져오세요. + + + 인증 코드가 전송되었습니다. + + + 인증 코드를 받지 못한 경우 support@github.com에 문의하세요. + + + 찾아보기 + + + Github.com에 연결할 수 없습니다. + + + 서버에 연결할 수 없습니다. + + + 만들기 + + + 설명(선택 사항) + + + 브라우저에서 열기 + + + 취소 + + + Gist 생성됨 + + + Gist를 만들지 못했습니다. + + + 기준 + + + 개인 정보 + + + 익명 사용 데이터를 보내 개선에 참여 + + + 클립보드에 복사할 수 없습니다. 다시 시도하세요. + + + 링크가 클립보드에 복사됨 + + + 리포지토리를 만들었습니다. + + + 개인 Gist + + + 파일 이름 + + + {0}에 로그인되지 않았으므로, 특정 Git 작업이 실패할 수 있습니다. [지금 로그인]({1}) + + + Wiki + + + Pulse + + + 경로 + + + 문제 + + + 그래프 + + + GitHub에 게시 + + + 오픈 소스 및 프라이빗 프로젝트에 대한 강력한 협업, 코드 검토 및 코드 관리. + + + 연결... + + + 복제 + + + 확인 + + + 2단계 인증 + + + 등록 + + + 로그아웃 + + + 등록된 SMS 디바이스에 코드를 다시 보냅니다. + + + 다시 보내기 + + + 리포지토리 이름 + + + 이 리포지토리에는 원격 항목이 없습니다. 양식을 작성하여 GitHub에 게시합니다. + + + 게시 + + + 또는 + + + 디바이스에서 2단계 인증 앱을 열어 인증 코드를 봅니다. + + + 리포지토리 없음 + + + 이름 + + + 비공개 리포지토리 + + + 로컬 경로: + + + 라이선스 + + + 자세한 정보 + + + Git ignore + + + 리포지토리 검색 + + + 일부 또는 전체 리포지토리가 로드되지 않았을 수 있습니다. 대화 상자를 닫고 다시 시도하세요. + + + 리포지토리를 로드하는 동안 오류가 발생했습니다. + + + GitHub Enterprise 서버 주소 + + + 호스트를 사용할 수 없거나 호스트가 GitHub Enterprise 서버가 아닙니다. 주소를 확인하고 다시 시도하세요. + + + 사용자 이름 또는 전자 메일 + + + 암호 + + + 로그인 + + + 인터넷 연결을 확인하고 다시 시도하세요. + + + GitHub Enterprise가 없으십니까? + + + 계정이 없으십니까? + + + 제목(필수) + + + 설명 + + + 이 리포지토리를 GitHub에 게시하고 오픈 소스 및 프라이빗 프로젝트에 대한 강력한 협업, 코드 검토 및 코드 관리를 가져옵니다. + + + 이 리포지토리가 GitHub에 없습니다. + + + 리포지토리 없음 + + + 여기에서 Git 리포지토리를 찾을 수 없습니다. Git 프로젝트를 열거나, 프로젝트에서 "파일 -> 소스 제어에 추가"를 클릭하여 시작합니다. + + + 계정 만들기 + + + 분기 필터링 + + + GitHub에 게시 + + + 시작 + + + 로그인 + + + 로그인... + + + 로컬 분기 최신 상태 + + + 변경 내용({0}개) + + + 변경 내용 보기 + + + 기본 작업으로 파일 비교 + + + 파일 보기 + + + 기본 작업으로 파일 열기 + + + 목록 보기로 전환 + + + 트리 뷰로 전환 + + + 업데이트된 {0} + + + GitHub에서 끌어오기 요청 보기 + + + Visual Studio용 GitHub를 시작합니다. [학습](show-training) 또는 [설명서](show-docs)를 살펴보세요. + +[다시 표시 안 함](dont-show-again) + + + 업데이트됨 + + + 편집기 여백에 PR 주석 표시 + + + 실험적 기능 + + + 이 기능은 향후 버전에서 변경될 수 있습니다. + + + 솔루션에서 변경 내용 보기 + + + 솔루션에서 파일 열기 + + + 토큰 + + + 검토 계속 + + + 검토 추가 + + + 검토자 + + + 검토 주석 추가 + + + 단일 주석 추가 + + + 포크 + + + 디버깅 + + + 추적 로깅 사용 + + + GitHub 확장은 Blend 내에서 사용할 수 없습니다. + + + 주석 업데이트 + + + 취소 + + + 보류 중 + + + 검토 시작 + + + 여기에 주석을 추가하려면 변경 내용을 커밋하고 푸시해야 합니다. + + + 이전 주석 + + + 다음 주석 + + + 끌어오기 요청 보기, 체크 아웃 또는 만들기 + + + 뒤로 + + + 리포지토리 포크 + + + 리포지토리 포크 + + + 로컬 리포지토리를 업데이트합니다. + + + 가리키려면 + + + 끌어오기 요청 만들기 + + + GitHub에 로그인 + + + 끌어오기 + + + 푸시 + + + 동기화 + + + 기록됨 + + + 다음에 대한 검토 제출 + + + 검토 요약 + + + 주석만 + + + 승인 + + + 변경 요청 + + + 주석 + + + 오래된 주석 + + + 새로 만들기 + + + 담당자 + + + 만든 사람 + + + 브라우저로 로그인 + + + 열기 + + + 작성자별 필터링 + + + 포크 선택 + + + 이 주석을 삭제하시겠습니까? + + + 주석 삭제 + + + 다시 시도 + + + 열린 끌어오기 요청이 없습니다. + + + 끌어오기 요청을 사용하면 GitHub의 리포지토리에 푸시한 변경 내용을 다른 사용자에게 알릴 수 있습니다. + + + 검색과 일치하는 결과가 없습니다. + + + 시작하기 위해 다음을 할 수 있습니다. + + + 끌어오기 요청 만들기 + + + '{0}'에서 리포지토리를 여시겠습니까? + + + 대상 URL의 소유자가 현재 리포지토리의 소유자와 다릅니다. + + + 탐색할 활성 리포지토리가 없습니다. + + + 작업 파일은 '{0}'에 있는 파일과 다릅니다. 해당 분기, 끌어오기 요청 또는 커밋을 체크 아웃하세요. + + + '{0}' 리포지토리를 열고 다시 시도하세요. + + + '{0}'에서 열 수 없습니다. 현재는 리포지토리 파일에 연결할 URL만 지원됩니다. + + + 클립보드에서 GitHub URL을 찾을 수 없습니다. + + + 현재 리포지토리에서 대상 URL을 찾을 수 없습니다. 페치를 수행한 후 다시 시도하세요. + + + 대상 경로의 디렉터리가 비어 있지 않습니다. + + + 리포지토리가 이 위치에 이미 있지만, "origin"이라는 원격 항목을 포함하지 않습니다. + + + 리포지토리가 이 위치에 이미 있지만, {0}의 원격 항목을 포함하지 않습니다. + + + 이 위치에 이미 복제했습니다. '열기'를 클릭하여 로컬 리포지토리를 엽니다. + + + 리포지토리의 GitHub URL을 찾을 수 없습니다. + + + 리포지토리에는 해당 GitHub URL을 찾기 위해 정의된 "origin"이라는 원격 항목이 있어야 합니다. + + + 'origin'에 대한 기존 원격 항목 중 하나의 이름을 변경하거나, 'origin'이라는 새 원격 항목을 추가하고 페치하세요. 이 작업은 명령줄에서 또는 아래 단추를 클릭하여 수행할 수 있습니다. + + + 리포지토리에서 '{0}'에 해당하는 파일을 찾을 수 없습니다. 'git fetch'를 수행하거나 대상 끌어오기 요청을 체크 아웃하세요. + + + {0}이(가) 가장 최근 푸시됨 + + + 주석 + + + 끌어오기 요청 닫기 + + + 문제 닫기 + + + 닫기 및 주석 처리 + + + 다시 열기 및 주석 처리 + + + 문제 다시 열기 + + + 끌어오기 요청 다시 열기 + + + {0}개 커밋 + + + 일부 커밋을 추가함 + + + 및 기타 + + + 이 대화는 확인된 것으로 표시되었습니다. + + + 사용자 리포지토리 + + + 협력자 리포지토리 + + + 리포지토리에 협력함 + + + URL 검색 또는 입력 + + + + 찾아보기... + + + 복제 + + + 계정 추가/변경 + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.pl-PL.resx b/src/GitHub.Resources/Resources.pl-PL.resx new file mode 100644 index 0000000000..b34e070d57 --- /dev/null +++ b/src/GitHub.Resources/Resources.pl-PL.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Wybierz folder zawierający dla nowego repozytorium. + + + Otwórz z witryny GitHub + + + Nie można nawiązać połączenia z witryną github.com + + + Utwórz gist usługi GitHub + + + Tworzenie repozytorium {0} + + + GistFromVisualStudio.cs + + + Wprowadź adres URL usługi Enterprise + + + Wprowadź prawidłowy adres URL usługi Enterprise + + + To nie jest serwer usługi Enterprise. Wprowadź adres URL usługi Enterprise + + + (Nie pamiętasz hasła?) + + + Upewnij się, że podczas logowania używasz hasła, a nie osobistego tokenu dostępu. + + + Sprawdź nazwę użytkownika i hasło i spróbuj ponownie + + + Nie można się zalogować. + + + Połącz z usługą GitHub + + + Wprowadź hasło + + + Pomyślnie utworzono żądanie ściągnięcia dla gałęzi **{0}**: [{1}]({2}) + + + Wprowadź tytuł żądania ściągnięcia + + + Gałąź źródłowa i docelowa nie mogą być takie same + + + Gałąź źródłowa nie istnieje zdalnie. Czy została wypchnięta? + + + Wprowadź ścieżkę repozytorium + + + Wprowadź prawidłową ścieżkę + + + Ścieżka zawiera nieprawidłowe znaki + + + Ścieżka jest za długa + + + Repozytorium „{0}/{1}” już istnieje. + + + Zmień nazwę repozytorium lub wybierz inne konto i spróbuj ponownie. + + + Przekroczono limit przydziału repozytoriów prywatnych. + + + Repozytorium o tej samej nazwie już istnieje w tej lokalizacji + + + Wprowadź nazwę repozytorium + + + Nazwa repozytorium musi zawierać mniej niż 100 znaków + + + Zostanie utworzone jako {0} + + + Otwórz aplikację uwierzytelniania dwuskładnikowego na urządzeniu, aby wyświetlić kod uwierzytelniania. + + + Wysłaliśmy Ci wiadomość SMS z kodem uwierzytelniania. + + + Wymagane jest uwierzytelnianie dwuskładnikowe + + + Wprowadź tutaj kod uwierzytelniania logowania + + + Wprowadź nazwę użytkownika lub adres e-mail + + + Nazwa użytkownika lub adres e-mail nie może zawierać spacji + + + Żądania ściągnięcia + + + Żądanie ściągnięcia + + + dodaj + + + rozwidlenie + + + [nieprawidłowa] + + + Aby móc wypchnąć, musisz najpierw ściągnąć + + + Brak zatwierdzeń do ściągnięcia + + + Brak zatwierdzeń do wypchnięcia + + + *Nie podano opisu.* + + + Wyewidencjonuj {0} + + + Wyewidencjonuj do: {0} + + + Ściągnij z {0} gałęzi {1} + + + Wypchnij do {0} gałęzi {1} + + + repozytorium zdalne + + + zmień nazwę + + + Repozytorium źródłowe nie jest już dostępne. + + + Nie można wyewidencjonować, ponieważ katalog roboczy zawiera niezatwierdzone zmiany. + + + Synchronizuj moduły podrzędne ({0}) + + + Nie można odnaleźć pliku Git.exe w ŚCIEŻCE. + +Zainstaluj narzędzie Git dla systemu Windows: +https://git-scm.com/download/win + + + Zatwierdzono + + + Żądano zmian + + + Dodano komentarz + + + W toku + + + Naciśnij klawisz Enter, aby przejść do edytora + + + Wyewidencjonuj gałąź żądania ściągnięcia przed przejściem do edytora + + + Naciśnij klawisz Enter, aby przejść do edytora (gałąź żądania ściągnięcia musi zostać wyewidencjonowana) + + + Utwórz rozwidlenie repozytorium + + + Przełącz źródło + + + Czy na pewno chcesz anulować ten przegląd? Utracisz wszystkie oczekujące komentarze. + + + Anuluj przegląd + + + Plik istnieje w ścieżce docelowej. + + + Wymagane wylogowanie + + + {0:N0} dzień temu + + + {0:N0} dni temu + + + {0:N0} godzinę temu + + + {0:N0} godz. temu + + + przed chwilą + + + {0:N0} minutę temu + + + {0:N0} min temu + + + {0:N0} miesiąc temu + + + {0:N0} mies. temu + + + {0:N0} sekundę temu + + + {0:N0} s temu + + + {0:N0} rok temu + + + {0:N0} l. temu + + + Nieprawidłowy kod uwierzytelniania + + + Spróbuj ponownie wprowadzić kod lub kliknąć przycisk Wyślij ponownie, aby uzyskać nowy kod uwierzytelniania. + + + Wysłano kod uwierzytelniania! + + + Jeśli nie otrzymasz kodu uwierzytelniania, wyślij wiadomość na adres support@github.com. + + + Przeglądaj + + + Nie można nawiązać połączenia z witryną github.com + + + Nie można nawiązać połączenia z serwerem. + + + Utwórz + + + Opis (opcjonalnie) + + + Otwórz w przeglądarce + + + Anuluj + + + Utworzono gist + + + Nie można utworzyć gistu + + + według + + + Prywatność + + + Pomóż nam w ulepszaniu produktu przez wysyłanie anonimowych danych użycia + + + Nie można skopiować do schowka. Spróbuj ponownie. + + + Skopiowano link do schowka + + + Pomyślnie utworzono repozytorium. + + + Gist prywatny + + + Nazwa pliku + + + Nie zalogowano Cię w produkcie {0}, więc niektóre operacje Git mogą kończyć się niepowodzeniem. [Zaloguj się teraz]({1}) + + + Witryna typu wiki + + + Puls + + + Ścieżka + + + Problemy + + + Wykresy + + + Publikowanie w usłudze GitHub + + + Zaawansowane funkcje współpracy, przeglądu kodu i zarządzania kodem dla prywatnych projektów i projektów typu open source. + + + Połącz… + + + Sklonuj + + + Weryfikuj + + + Uwierzytelnianie dwuskładnikowe + + + Utwórz konto + + + Wyloguj się + + + Ponownie wyślij kod do zarejestrowanego urządzenia obsługującego wiadomości SMS + + + Wyślij ponownie + + + Nazwa repozytorium + + + To repozytorium nie ma repozytorium zdalnego. Wypełnij formularz, aby opublikować je w witrynie GitHub. + + + Publikuj + + + lub + + + Otwórz aplikację uwierzytelniania dwuskładnikowego na urządzeniu, aby wyświetlić kod uwierzytelniania. + + + Brak repozytoriów + + + Nazwa + + + Repozytorium prywatne + + + Ścieżka lokalna: + + + Licencja + + + Dowiedz się więcej + + + Git — ignoruj + + + Wyszukaj repozytoria + + + Być może nie załadowano niektórych lub wszystkich repozytoriów. Zamknij okno dialogowe i spróbuj ponownie. + + + Wystąpił błąd podczas ładowania repozytoriów + + + Adres serwera usługi GitHub Enterprise + + + Host jest niedostępny lub nie jest serwerem usługi GitHub Enterprise. Sprawdź adres i spróbuj ponownie. + + + Nazwa użytkownika lub adres e-mail + + + Hasło + + + Zaloguj + + + Sprawdź połączenie internetowe i spróbuj ponownie. + + + Nie masz usługi GitHub Enterprise? + + + Nie masz konta? + + + Tytuł (wymagany) + + + Opis + + + Opublikuj to repozytorium w usłudze GitHub i korzystaj z zaawansowanych funkcji współpracy, przeglądu kodu i zarządzania kodem dla prywatnych projektów i projektów typu open source. + + + Tego repozytorium nie ma w witrynie GitHub + + + Brak repozytorium + + + Nie można odnaleźć tutaj repozytorium Git. Otwórz projekt Git lub kliknij w projekcie pozycję „Plik -> Dodaj do kontroli źródła”, aby rozpocząć. + + + Utwórz konto + + + Filtruj gałęzie + + + Opublikuj w usłudze GitHub + + + Wprowadzenie + + + Zaloguj + + + Zaloguj... + + + Gałąź lokalna jest aktualna + + + Zmiany ({0}) + + + Wyświetl zmiany + + + Porównanie pliku jako akcja domyślna + + + Wyświetl plik + + + Otwarcie pliku jako akcja domyślna + + + Przełącz do widoku listy + + + Przełącz do widoku drzewa + + + zaktualizowano {0} + + + Wyświetl żądanie ściągnięcia w witrynie GitHub + + + Witamy w usłudze GitHub for Visual Studio! Zapoznaj się naszym [szkoleniem](show-training) lub [dokumentacją](show-docs). + +[Nie pokazuj ponownie](dont-show-again) + + + Zaktualizowane + + + Pokaż komentarze dotyczące żądania ściągnięcia na marginesie edytora + + + Funkcje eksperymentalne + + + Te funkcje mogą ulec zmianie w przyszłej wersji + + + Wyświetl zmiany w rozwiązaniu + + + Otwórz plik w rozwiązaniu + + + Token + + + Kontynuuj przegląd + + + Dodaj przegląd + + + Recenzenci + + + Dodaj komentarz do przeglądu + + + Dodaj jeden komentarz + + + Rozwidlenie + + + Debugowanie + + + Włącz rejestrowanie śledzenia + + + Rozszerzenie GitHub jest niedostępne w programie Blend + + + Zaktualizuj komentarz + + + Anuluj + + + Oczekujący + + + Rozpocznij przegląd + + + Musisz zatwierdzić i wypchnąć zmiany, aby dodać tutaj komentarz. + + + Poprzedni komentarz + + + Następny komentarz + + + Wyświetl, wyewidencjonuj lub utwórz żądanie ściągnięcia + + + Wstecz + + + Utwórz rozwidlenie repozytorium + + + Utwórz rozwidlenie repozytorium + + + Aktualizuj repozytorium lokalne + + + w celu wskazywania + + + Utwórz żądanie ściągnięcia + + + Zaloguj się w witrynie GitHub + + + Ściągnij + + + Wypchnij + + + Synchronizacja + + + zapisano + + + Prześlij przegląd do + + + Podsumowanie przeglądu + + + Tylko komentarz + + + Zatwierdź + + + Żądaj zmian + + + Komentarze + + + Nieaktualne komentarze + + + Utwórz nowy + + + Osoba przydzielona + + + Autor + + + Zaloguj się przy użyciu przeglądarki + + + Otwórz + + + Filtruj według autora + + + Wybierz rozwidlenie + + + Czy na pewno chcesz usunąć ten komentarz? + + + Usuń komentarz + + + Ponów próbę + + + Nie ma żadnych otwartych żądań ściągnięcia + + + Żądania ściągnięcia umożliwiają informowanie o zmianach wypchniętych do repozytorium w usłudze GitHub + + + Brak wyników spełniających kryteria wyszukiwania. + + + Aby rozpocząć, możesz + + + utwórz żądanie ściągnięcia + + + Otworzyć repozytorium w lokalizacji „{0}”? + + + Docelowy adres URL ma innego właściciela bieżącego repozytorium. + + + Brak aktywnego repozytorium do nawigacji + + + Plik roboczy różni się od pliku w lokalizacji „{0}”. Wyewidencjonuj odpowiednią gałąź, żądanie ściągnięcia lub zatwierdzenie. + + + Otwórz repozytorium „{0}” i spróbuj ponownie + + + Nie można otworzyć z: „{0}”. Obecnie obsługiwane są tylko adresy URL prowadzące do plików repozytorium. + + + Nie można odnaleźć adresu URL usługi GitHub w schowku + + + Nie można odnaleźć docelowego adresu URL w bieżącym repozytorium. Spróbuj ponownie po zakończeniu pobierania. + + + Katalog w ścieżce docelowej nie jest pusty. + + + Repozytorium już istnieje w tej lokalizacji, ale nie ma zdalnego repozytorium o nazwie „origin”. + + + Repozytorium już istnieje w tej lokalizacji, ale ma repozytorium zdalne {0}. + + + Sklonowano już zawartość do tej lokalizacji. Kliknij pozycję „Otwórz”, aby otworzyć repozytorium lokalne. + + + Nie można odnaleźć adresu URL repozytorium w usłudze GitHub + + + Repozytoria muszą mieć zdefiniowane repozytorium zdalne o nazwie „origin” w celu zlokalizowania ich adresu URL usługi GitHub. + + + Zmień nazwę jednego z istniejących repozytoriów zdalnych na „origin” lub dodaj nowe repozytorium zdalne o nazwie „origin” i pobierz. Można to zrobić z poziomu wiersza polecenia lub przez kliknięcie przycisku poniżej. + + + Nie można odnaleźć pliku odpowiadającego elementowi „{0}” w repozytorium. Wykonaj polecenie pobierania danych Git lub wyewidencjonuj docelowe żądanie ściągnięcia. + + + Ostatnio wypchnięte ({0}) + + + Komentarz + + + Zamknij żądanie ściągnięcia + + + Zamknij problem + + + Zamknij i skomentuj + + + Otwórz ponownie i skomentuj + + + Otwórz ponownie problem + + + Otwórz ponownie żądanie ściągnięcia + + + Zatwierdzenia w {0} + + + — użytkownik dodał zatwierdzenia + + + i inni + + + Ta konwersacja została oznaczona jako rozwiązana + + + Twoje repozytoria + + + Repozytoria współpracowników + + + Współtworzone repozytoria + + + Wyszukaj lub wprowadź adres URL + + + + Przeglądaj... + + + Klonuj + + + Dodaj/Zmień konta + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.pt-BR.resx b/src/GitHub.Resources/Resources.pt-BR.resx new file mode 100644 index 0000000000..b74ea550ff --- /dev/null +++ b/src/GitHub.Resources/Resources.pt-BR.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Selecione uma pasta de contenção para o seu novo repositório. + + + Abrir do GitHub + + + Não foi possível conectar ao github.com + + + Criar um Gist do GitHub + + + Criar um Repositório {0} + + + GistFromVisualStudio.cs + + + Insira uma URL do Enterprise + + + Insira uma URL do Enterprise válida + + + Não é um servidor do Enterprise. Insira uma URL do Enterprise + + + (esqueceu a senha?) + + + Use sua senha e não um token de Acesso Pessoal para entrar. + + + Verifique suas informações de nome de usuário e senha e tente novamente + + + Falha ao entrar. + + + Conectar-se Ao GitHub + + + Insira sua senha + + + Solicitação de Pull para branch **{0}** criada com êxito em [{1}]({2}) + + + Insira um título para a Solicitação de Pull + + + O branch de origem e de destino não podem ser o mesmo + + + O branch de origem não existe remotamente. Você o enviou por push? + + + Insira um caminho de repositório + + + Insira um caminho válido + + + O caminho contém caracteres inválidos + + + O caminho é muito longo + + + O repositório '{0}/{1}' já existe. + + + Altere o nome do repositório ou selecione uma conta diferente e tente novamente. + + + Ultrapassou a cota de repositórios privada. + + + Já existe um repositório com o mesmo nome neste local + + + Insira um nome de repositório + + + O nome do repositório deve ter menos de 100 caracteres + + + Será criado como {0} + + + Abra o aplicativo de autenticação de dois fatores em seu dispositivo para exibir o código de autenticação. + + + Enviamos uma mensagem para você por SMS com o código de autenticação. + + + É necessária a autenticação de Dois Fatores + + + Insira um sinal no código de autenticação aqui + + + Insira seu nome de usuário ou endereço de email + + + O nome de usuário ou endereço de email não deve ter espaços + + + Solicitações Pull + + + Solicitação pull + + + adicionar + + + bifurcação + + + [inválido] + + + Você deve efetuar pull antes de poder enviar por push + + + Não há commits dos quais efetuar pull + + + Não há commits a serem enviados por push + + + *Nenhuma descrição fornecida.* + + + Fazer check-out de {0} + + + Fazer check-out para {0} + + + Efetuar pull de {0} branch {1} + + + Enviar por push para {0} branch {1} + + + repositório remoto + + + renomear + + + O repositório de origem não está mais disponível. + + + Não é possível fazer check-out, pois o seu diretório de trabalho tem alterações cujo commit não foi feito. + + + Sincronizar {0} submódulos + + + Não foi possível encontrar Git.exe no CAMINHO. + +Instale o Git para Windows: +https://git-scm.com/download/win + + + Aprovado + + + Alterações Solicitadas + + + Comentado + + + EmAndamento + + + Pressione Enter para navegar para o Editor + + + Fazer check-out do branch de PR antes de navegar para o Editor + + + Pressione Enter para navegar para o Editor (é necessário fazer o check-out do branch de PR) + + + Repositório de Bifurcação + + + Alternar Origem + + + Tem certeza de que deseja cancelar esta revisão? Você perderá todos os seus comentários pendentes. + + + Cancelar Revisão + + + Existe um arquivo no caminho de destino. + + + Logoff Necessário + + + {0:N0} dia atrás + + + {0:N0} dias atrás + + + {0:N0} hora atrás + + + {0:N0} horas atrás + + + agora + + + {0:N0} minuto atrás + + + {0:N0} minutos atrás + + + {0:N0} mês atrás + + + {0:N0} meses atrás + + + {0:N0} segundo atrás + + + {0:N0} segundos atrás + + + {0:N0} ano atrás + + + {0:N0} anos atrás + + + Código de autenticação inválido + + + Tente inserir o código novamente ou clicar no botão de reenvio para obter um novo código de autenticação. + + + Código de autenticação enviado. + + + Se você não receber o código de autenticação, entre em contato com support@github.com. + + + Procurar + + + Não foi possível conectar ao github.com + + + Não foi possível conectar ao servidor. + + + Criar + + + Descrição (Opcional) + + + Abrir no Navegador + + + Cancelar + + + Gist criado + + + Falha ao criar gist + + + por + + + Privacidade + + + Ajude-nos a melhorar enviando dados de uso anônimos + + + Não foi possível copiar para a área de transferência. Tente novamente. + + + Link copiado para a área de transferência + + + Repositório criado com êxito. + + + Gist Privado + + + Nome do Arquivo + + + Você não entrou em {0}; portanto, determinadas operações git poderão falhar. [Entre agora]({1}) + + + Wiki + + + Pulso + + + Caminho + + + Questões + + + Grafos + + + Publicar no GitHub + + + Colaboração avançada, revisão de código e gerenciamento de código para projetos privados e de software livre. + + + Conectar… + + + Clonar + + + Verificar + + + Autenticação em dois fatores + + + Inscrever-se + + + Sair + + + Enviar o código novamente para o seu Dispositivo de SMS registrado + + + Reenviar + + + Nome do Repositório + + + Este repositório não tem um repositório remoto. Preencha o formulário para publicá-lo no GitHub. + + + Publicar + + + ou + + + Abra o aplicativo de autenticação de dois fatores em seu dispositivo para exibir o código de autenticação. + + + Nenhum repositório + + + Nome + + + Repositório Privado + + + Caminho local: + + + Licença + + + Saiba mais + + + Ignorar git + + + Pesquisar repositórios + + + Alguns ou todos os repositórios podem não ter carregado. Feche a caixa de diálogo e tente novamente. + + + Ocorreu um erro ao carregar os repositórios + + + Endereço do servidor do GitHub Enterprise + + + O host não está disponível ou não é um servidor do GitHub Enterprise. Verifique o endereço e tente novamente. + + + Nome de usuário ou email + + + Senha + + + Entrar + + + Verifique sua conexão com a Internet e tente novamente. + + + Não tem o GitHub Enterprise? + + + Não tem uma conta? + + + Título (obrigatório) + + + Descrição + + + Publique este repositório no GitHub e obtenha colaboração avançada, revisão de código e gerenciamento de código para projetos privados e de software livre. + + + Este repositório não está no GitHub + + + Nenhum repositório + + + Não foi possível localizar um repositório git aqui. Abra um projeto git ou clique em "Arquivo -> Adicionar ao Controle do Código-fonte" em um projeto para começar. + + + Criar uma conta + + + Filtrar branches + + + Publicar no GitHub + + + Introdução + + + Entrar + + + Entrar... + + + Branch local atualizado + + + Alterações ({0}) + + + Exibir alterações + + + Comparar o Arquivo como Ação Padrão + + + Exibir Arquivo + + + Abrir o Arquivo como Ação Padrão + + + Alternar para o Modo de Exibição de Lista + + + Alternar para o Modo de Exibição de Árvore + + + atualizou {0} + + + Exibir Solicitação de Pull no GitHub + + + Bem-vindo ao GitHub para Visual Studio! Por que não dá uma olhada em nosso [treinamento](show-training) ou [documentação](show-docs)? + +[Não mostrar esta mensagem novamente](dont-show-again) + + + Atualizado + + + Mostrar comentários de PR na margem do editor + + + Recursos experimentais + + + Estes recursos poderão ser alterados em uma versão futura + + + Exibir Alterações na Solução + + + Abrir o Arquivo na Solução + + + Token + + + Continuar sua revisão + + + Adicionar sua revisão + + + Revisores + + + Adicionar comentário de revisão + + + Adicionar um único comentário + + + Bifurcação + + + Depurando + + + Habilitar o Registro de Rastreamento + + + A extensão do GitHub não está disponível dentro do Blend + + + Atualizar o comentário + + + Cancelar + + + Pendente + + + Iniciar uma revisão + + + Você precisa fazer commit das alterações e enviá-las por push para adicionar um comentário aqui. + + + Comentário Anterior + + + Próximo Comentário + + + Exibir, Fazer Check-out ou Criar uma solicitação de Pull + + + Voltar + + + Repositório de Bifurcação + + + Bifurcar o repositório + + + Atualizar seu repositório local + + + para apontar para + + + Criar solicitação de pull + + + Entrar no GitHub + + + Efetuar pull + + + Enviar por push + + + Sincronizar + + + gravou + + + Enviar a sua revisão para + + + O resumo da sua revisão + + + Comentário somente + + + Aprovar + + + Solicitar alterações + + + Comentários + + + Comentários desatualizados + + + Criar Novo + + + Destinatário + + + Autor + + + Entrar com seu navegador + + + Abrir + + + Filtrar por Autor + + + Selecionar Bifurcação + + + Tem certeza de que deseja excluir este comentário? + + + Excluir Comentário + + + Tentar Novamente + + + Não há nenhuma solicitação de pull aberta + + + As solicitações de pull permitem que você informe outras pessoas sobre as alterações que você enviou por push a um repositório no GitHub + + + Nenhum resultado correspondeu à sua pesquisa. + + + Para começar, você pode + + + criar uma solicitação de pull + + + Abrir repositório em '{0}'? + + + A URL de destino tem um proprietário diferente para o repositório atual. + + + Não há nenhum repositório ativo para navegar + + + O arquivo de trabalho é diferente do arquivo em '{0}'. Faça o check-out do branch, da solicitação pull ou do commit correspondente. + + + Abra o repositório '{0}' e tente novamente + + + Não foi possível abrir pelo '{0}'. No momento, há suporte para apenas as URLs vinculadas aos arquivos do repositório. + + + Não foi possível localizar uma URL do GitHub na área de transferência + + + Não foi possível encontrar a URL de destino no repositório atual. Tente novamente depois de efetuar um fetch. + + + O diretório no caminho de destino não está vazio. + + + Já existe um repositório neste local, mas ele não tem um repositório remoto com o nome "origem". + + + Já existe um repositório neste local, mas ele tem um repositório remoto de {0}. + + + Você já clonou nesse local. Clique em 'Abrir' para abrir o repositório local. + + + Não é possível encontrar a URL do GitHub para repositório + + + Os repositórios precisam ter um repositório remoto chamado "origem" definido para localizar suas URLs do GitHub. + + + Renomeie um dos seus repositórios remotos existentes para 'origem' ou adicione um novo repositório remoto chamado 'origem' e efetue um fetch. Isso pode ser feito da linha de comando ou clicando no botão abaixo. + + + Não foi possível encontrar o arquivo correspondente a '{0}' no repositório. Faça um 'git fetch' ou o check-out da solicitação de pull de destino. + + + {0} mais recentemente pressionado + + + Comentário + + + Fechar solicitação de pull + + + Fechar problema + + + Fechar e comentar + + + Reabrir e comentar + + + Reabrir problema + + + Reabrir solicitação de pull + + + {0} confirma + + + adicionou algumas confirmações + + + e outros + + + Esta conversa foi marcada como resolvida + + + Seus repositórios + + + Repositórios de colaborador + + + Contribui para os repositórios + + + Pesquise ou insira uma URL + + + + Procurar... + + + Clonar + + + Adicionar/Alterar Contas + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.resx b/src/GitHub.Resources/Resources.resx index d8ff9d14ba..eef3e7efed 100644 --- a/src/GitHub.Resources/Resources.resx +++ b/src/GitHub.Resources/Resources.resx @@ -120,9 +120,6 @@ Select a containing folder for your new repository. - - Clone a Repository - Open from GitHub @@ -168,12 +165,6 @@ Pull Request for branch **{0}** created successfully at [{1}]({2}) - - Publish repository - - - Publish repository to {0} - Please enter a title for the Pull Request @@ -183,9 +174,6 @@ Source branch doesn't exist remotely, have you pushed it? - - No selected repository. - Please enter a repository path @@ -511,7 +499,7 @@ https://git-scm.com/download/win Private Repository - Local path + Local path: License @@ -674,9 +662,6 @@ https://git-scm.com/download/win The GitHub extension is not available inside Blend - - Show Fork button in Team Explorer - Update comment @@ -810,7 +795,7 @@ https://git-scm.com/download/win There is no active repository to navigate - This file has changed since the permalink was created + The working file is different to the file at '{0}'. Please checkout the corresponding branch, pull request or commit. Please open the repository '{0}' and try again @@ -824,8 +809,8 @@ https://git-scm.com/download/win Couldn't find target URL in current repository. Try again after doing a fetch. - - There is already a directory at this location, but it doesn't contain a repository. + + The directory at the destination path is not empty. A repository already exists at this location, but it doesn't have a remote named "origin". @@ -836,4 +821,74 @@ https://git-scm.com/download/win You have already cloned to this location. Click 'Open' to open the local repository. + + Can't find GitHub URL for repository + + + Repositories must have a remote called "origin" defined in order to locate their GitHub URL. + + + Please rename one of your existing remotes to 'origin' or add a new remote named 'origin' and fetch. This can be done from the command line or by clicking the button below. + + + Couldn't find file corresponding to '{0}' in the repository. Please do a 'git fetch' or checkout the target pull request. + + + {0} most recently pushed + + + Comment + + + Close pull request + + + Close issue + + + Close and comment + + + Reopen and comment + + + Reopen issue + + + Reopen pull request + + + {0} commits + + + added some commits + + + and others + + + This conversation was marked as resolved + + + Your repositories + + + Collaborator repositories + + + Contributed to repositories + + + Search or enter a URL + + + + Browse... + + + Clone + + + Add/Change Accounts + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.ru-RU.resx b/src/GitHub.Resources/Resources.ru-RU.resx new file mode 100644 index 0000000000..3c3511344c --- /dev/null +++ b/src/GitHub.Resources/Resources.ru-RU.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Выберите папку для нового репозитория. + + + Открыть из GitHub + + + Не удалось подключиться к github.com + + + Создать GitHub gist + + + Создать {0} репозиторий + + + GistFromVisualStudio.cs + + + Введите URL-адрес предприятия + + + Введите допустимый URL-адрес Enterprise + + + Это не сервер предприятия. Введите URL-адрес предприятия + + + (Забыли пароль?) + + + Используйте для входа только свой пароль, а не личный маркер доступа. + + + Проверьте имя пользователя и пароль и повторите попытку. + + + Сбой входа. + + + Подключиться к GitHub + + + Введите пароль + + + Запрос на вытягивание для ветви **{0}** успешно создан в [{1}]({2}) + + + Введите заголовок для запроса на вытягивание + + + Исходная и целевая ветви не могут совпадать + + + Исходная ветвь не существует удаленно, вы отправили ее? + + + Введите путь к репозиторию + + + Введите допустимый путь + + + Путь содержит недопустимые символы + + + Слишком длинный путь + + + Репозиторий "{0}/{1}" уже существует. + + + Измените имя репозитория или выберите другую учетную запись и повторите попытку. + + + Превышена квота для частных репозиториев. + + + Репозиторий с таким именем уже существует в этом расположении + + + Введите имя репозитория. + + + Имя репозитория должно содержать меньше 100 символов + + + Будет создан как {0} + + + Откройте приложение двухфакторной проверки подлинности на устройстве, чтобы просмотреть код проверки подлинности. + + + Мы отправили вам SMS с кодом проверки подлинности. + + + Требуется двухфакторная проверка подлинности + + + Введите знак в коде проверки подлинности + + + Введите имя пользователя или адрес электронной почты + + + Имя пользователя или адрес электронной почты не должны содержать пробелы + + + Запросы на вытягивание + + + Запрос на вытягивание + + + добавлено + + + вилка + + + [недопустимый] + + + Нужно вытянуть, прежде чем отправлять + + + Нет фиксаций для вытягивания + + + Нет фиксаций для отправки + + + *Описание не указано.* + + + Извлечение {0} + + + Извлечь в {0} + + + Вытянуть из ветви {0} {1} + + + Отправить в ветвь {0} {1} + + + удаленный + + + переименование + + + Репозиторий исходного кода больше не доступен. + + + Не удается извлечь, так как рабочий каталог имеет незафиксированные изменения. + + + Синхронизация подмодулей ({0}) + + + Не удалось найти Git.exe по пути в переменной PATH. + +Установите Git для Windows со страницы: +https://git-scm.com/download/win + + + Утверждено + + + Запрошены изменения + + + Комментарий добавлен + + + Выполняется + + + Нажмите клавишу ВВОД для перехода в редактор + + + Извлечь ветвь PR до перехода в редактор + + + Нажмите клавишу ВВОД для перехода в редактор (ветвь PR должна быть извлечена) + + + Создать вилку репозитория + + + Переключить origin + + + Вы действительно хотите отменить эту проверку? Все ожидающие комментарии будут потеряны. + + + Отменить проверку + + + В пути назначения существует файл. + + + Требуется выход из системы + + + {0:N0} дн. назад + + + {0:N0} дн. назад + + + {0:N0} ч назад + + + {0:N0} ч назад + + + сейчас + + + {0:N0} мин назад + + + {0:N0} мин назад + + + {0:N0} мес. назад + + + {0:N0} мес. назад + + + {0:N0} с назад + + + {0:N0} с назад + + + {0:N0} г. назад + + + {0:N0} года/лет назад + + + Недопустимый код проверки подлинности + + + Попробуйте ввести код еще раз или нажать кнопку повторной отправки, чтобы получить новый код проверки подлинности. + + + Код проверки подлинности отправлен. + + + Если вы не получите код проверки подлинности, обратитесь по адресу support@github.com. + + + Обзор + + + Не удалось подключиться к github.com + + + Не удалось подключиться к серверу. + + + Создать + + + Описание (необязательно) + + + Открыть в браузере + + + Отмена + + + Gist создан + + + Не удалось создать gist + + + по + + + Конфиденциальность + + + Помогите нам улучшить продукт, отправляя анонимные данные об использовании + + + Не удалось скопировать в буфер обмена. Повторите попытку. + + + Ссылка скопирована в буфер обмена + + + Репозиторий успешно создан. + + + Частный gist + + + Имя файла + + + Вы не вошли в {0}, поэтому некоторые операции Git могут завершиться сбоем. [Войти в систему]({1}) + + + Вики-сайт + + + Импульс + + + Путь + + + Вопросы + + + Диаграммы + + + Опубликовать в GitHub + + + Эффективные средства совместной работы, проверки кода и управления кодом для закрытых проектов и проектов открытого кода. + + + Подключиться… + + + Клонировать + + + Проверить + + + Двухфакторная проверка подлинности + + + Зарегистрироваться + + + Выход + + + Снова отправить код на зарегистрированное устройство для SMS + + + Повторно отправить + + + Имя репозитория + + + У этого репозитория нет удаленного репозитория. Заполните форму на публикацию в GitHub. + + + Опубликовать + + + или + + + Откройте приложение двухфакторной проверки подлинности на устройстве, чтобы просмотреть код проверки подлинности. + + + Нет репозиториев + + + Имя + + + Частный репозиторий + + + Локальный путь: + + + Лицензия + + + Дополнительные сведения + + + Игнорировать Git + + + Поиск по репозиториям + + + Не были загружены некоторые или все репозитории. Закройте это диалоговое окно и повторите попытку. + + + Произошла ошибка при загрузке панелей репозиториев + + + Адрес сервера GitHub Enterprise + + + Узел недоступен или не является сервером GitHub Enterprise. Проверьте адрес и повторите попытку. + + + Имя пользователя или адрес электронной почты + + + Пароль + + + Войти + + + Проверьте подключение к Интернету и повторите попытку. + + + У вас нет GitHub Enterprise? + + + У вас нет учетной записи? + + + Заголовок (обязательно) + + + Описание + + + Опубликуйте этот репозиторий в GitHub и получите эффективные средства совместной работы, проверки кода и управления кодом для закрытых проектов и проектов открытого кода. + + + Этот репозиторий находится не в GitHub + + + Репозиторий отсутствует. + + + Не удалось найти здесь репозиторий Git. Откройте проект Git или выберите "Файл" -> "Добавить в систему управления версиями" в проекте, чтобы приступить к работе. + + + Создать учетную запись + + + Фильтровать ветви + + + Опубликовать в GitHub + + + Начало работы + + + Войти + + + Войти... + + + Локальная ветвь актуальна + + + Изменения ({0}) + + + Просмотр изменений + + + Сравнить файл в качестве действия по умолчанию + + + Просмотр файла + + + Открыть файл в качестве действия по умолчанию + + + Перейти к представлению списка + + + Перейти к представлению в виде дерева + + + обновлен {0} + + + Просмотреть запрос на вытягивание в GitHub + + + Добро пожаловать в GitHub для Visual Studio. Рекомендуем вам ознакомиться с нашим [обучением](show-training) или нашей [документацией](show-docs)? + +[Больше не показывать](dont-show-again) + + + Обновлено + + + Показать комментарии PR на поле окна редактора + + + Экспериментальные функции + + + Эти компоненты могут быть изменены в будущей версии + + + Просмотреть изменения в решении + + + Открыть файл в решении + + + Токен + + + Продолжайте проверку + + + Добавьте проверку + + + Рецензенты + + + Добавить комментарий к проверке + + + Добавить отдельный комментарий + + + Вилка + + + Отладка + + + Включить ведение журнала трассировки + + + Расширение GitHub недоступно внутри Blend + + + Обновить комментарий + + + Отмена + + + Ожидание + + + Начать проверку + + + Вам нужно зафиксировать и отправить изменения, чтобы добавить сюда комментарий. + + + Предыдущий комментарий + + + Следующий комментарий + + + Просмотр, извлечение или создание запроса на вытягивание + + + Назад + + + Создать вилку репозитория + + + Создать вилку репозитория + + + В своем локальном репозитории обновите + + + для указания на + + + Создать запрос на вытягивание + + + Войти в GitHub + + + Вытянуть + + + Отправить + + + Синхронизировать + + + записал + + + Отправьте результаты проверки для + + + Сводка по вашей проверке + + + Только комментарий + + + Утвердить + + + Запросить изменения + + + Комментарии + + + Устаревшие комментарии + + + Создать + + + Уполномоченное лицо + + + Автор + + + Войдите с помощью браузера + + + Открыть + + + Фильтровать по автору + + + Выберите вилку + + + Действительно удалить этот комментарий? + + + Удаление комментария + + + Повторить + + + Нет открытых запросов на вытягивание + + + Запросы на вытягивание позволяют сообщить другим об изменениях, отправленных вами в репозиторий в GitHub + + + Нет результатов, отвечающих условиям поиска. + + + Чтобы приступить к работе, вы можете + + + создать запрос на вытягивание + + + Открыть репозиторий в "{0}"? + + + Целевой URL-адрес имеет другого владельца для текущего репозитория. + + + Отсутствует активный репозиторий для перехода + + + Рабочий файл отличается от файла в "{0}". Извлеките соответствующую ветвь, фиксацию или запрос на вытягивание. + + + Откройте репозиторий "{0}" и повторите попытку + + + Не удалось открыть из "{0}". Сейчас поддерживаются только URL-адреса, ссылающиеся на файлы репозитория. + + + Не удалось найти URL-адрес GitHub в буфере обмена + + + Не удалось найти целевой URL-адрес в текущем репозитории. Выполните принесение и повторите попытку. + + + Каталог в пути назначения не пуст. + + + В этом расположении уже существует репозиторий, но у него отсутствует удаленный репозиторий с именем "origin". + + + В этом расположении уже существует репозиторий, но у него имеется удаленный репозиторий {0}. + + + Вы уже клонировали элементы в это расположение. Нажмите кнопку "Открыть", чтобы открыть локальный репозиторий. + + + Не удается найти URL-адрес GitHub для репозитория + + + У репозиториев должен быть определен удаленный репозиторий "origin" для обнаружения их URL-адреса GitHub. + + + Переименуйте один из существующих удаленных репозиториев в "origin" или добавьте новый удаленный репозиторий с такими именем и выполните принесение. Это можно сделать с помощью командной строки или приведенной ниже кнопки. + + + Не удалось найти соответствующий файл "{0}" в репозитории. Выполните команду "git fetch" или извлечение целевого запроса на вытягивание. + + + Отправлено последний раз: {0} + + + Комментарий + + + Закрыть запрос на вытягивание + + + Закрыть обращение + + + Закрыть и прокомментировать + + + Повторно открыть и прокомментировать + + + Повторно открыть обращение + + + Повторно открыть запрос на включение внесенных изменений + + + Фиксаций: {0} + + + добавляет некоторые фиксации + + + и др. + + + Эта беседа была помечена как разрешенная. + + + Ваши хранилища + + + Репозитории участника совместной работы + + + Добавлял данные в репозитории + + + Поиск или ввод URL-адреса + + + + Обзор... + + + Клонировать + + + Добавить или изменить учетные записи + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.tr-TR.resx b/src/GitHub.Resources/Resources.tr-TR.resx new file mode 100644 index 0000000000..6b671d0c55 --- /dev/null +++ b/src/GitHub.Resources/Resources.tr-TR.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Yeni deponuzu içerecek klasörü seçin. + + + GitHub'dan Aç + + + github.com ile bağlantı kurulamadı + + + GitHub Gist oluştur + + + {0} Deposu Oluştur + + + GistFromVisualStudio.cs + + + Lütfen bir Enterprise URL'si girin + + + Lütfen geçerli bir Enterprise URL'si girin + + + Bu bir Enterprise sunucusu değil. Lütfen bir Enterprise URL'si girin + + + (parolanızı mı unuttunuz?) + + + Oturum açmak için Kişisel Erişim belirteci yerine parolanızı kullandığınızdan emin olun. + + + Kullanıcı adınızı ve parolanızı kontrol edin, sonra tekrar deneyin + + + Oturum açılamadı. + + + GitHub'a Bağlan + + + Lütfen parolanızı girin + + + **{0}** dalı için Çekme İsteği [{1}] ({2}) konumunda başarıyla oluşturuldu + + + Lütfen Çekme İsteği için bir başlık girin + + + Kaynak ve hedef dal aynı olamaz + + + Kaynak dal uzak depoda yok, kaynak dalı gönderdiniz mi? + + + Lütfen bir depo yolu girin + + + Lütfen geçerli bir yol girin + + + Yol geçersiz karakterler içeriyor + + + Yol çok uzun + + + '{0}/{1}' deposu zaten var. + + + Depo adını değiştirin veya farklı bir hesap seçip yeniden deneyin. + + + Özel depolar kotası aşıldı. + + + Bu konumda aynı ada sahip bir depo zaten var + + + Lütfen bir depo adı girin + + + Depo adı 100 karakterden kısa olmalıdır + + + {0} olarak oluşturulacak + + + Kimlik doğrulama kodunuzu görüntülemek için cihazınızda iki öğeli kimlik doğrulama uygulamasını açın. + + + Size SMS üzerinden kimlik doğrulama kodunuzun bulunduğu bir mesaj gönderdik. + + + İki öğeli kimlik doğrulama gerekiyor + + + Buraya bir oturum açma kimlik doğrulama kodu girin + + + Lütfen kullanıcı adınızı veya e-posta adresinizi girin + + + Kullanıcı adı veya e-posta adresi boşluk içeremez + + + Çekme İstekleri + + + Çekme İsteği + + + ekleyin + + + çatal oluştur + + + [geçersiz] + + + Gönderebilmeniz için önce çekmeniz gerekir + + + Çekilecek commit yok + + + Gönderilecek commit yok + + + *Açıklama sağlanmadı.* + + + Şunu kullanıma al: {0} + + + Kullanıma alma hedefi: {0} + + + {0} {1} dalından çekin + + + {0} {1} dalına gönder + + + uzak depo + + + yeniden adlandır + + + Kaynak depo artık kullanılamıyor. + + + Çalışma dizininizde kaydedilmemiş değişiklikler olduğundan kullanıma alınamıyor. + + + {0} alt modüllerini eşitle + + + PATH konumunda Git.exe dosyası bulunamadı. + +Lütfen şuradan Windows için Git yükleyin: +https://git-scm.com/download/win + + + Onaylandı + + + İstenen Değişiklikler + + + Açıklamalı + + + İlerliyor + + + Düzenleyiciye gitmek için Enter tuşuna basın + + + Düzenleyiciye gitmeden önce PR dalını kullanıma alın + + + Düzenleyiciye gitmek için Enter tuşuna basın (PR dalının kullanıma alınması gerekir) + + + Depo Çatalı Oluştur + + + Origin'i Değiştir + + + Bu incelemeyi iptal etmek istediğinizden emin misiniz? Tüm bekleyen açıklamalarınızı kaybedersiniz. + + + İncelemeyi İptal Et + + + Hedef yolunda bir dosya var. + + + Oturumun Kapatılması Gerekiyor + + + {0:N0} gün önce + + + {0:N0} gün önce + + + {0:N0} saat önce + + + {0:N0} saat önce + + + az önce + + + {0:N0} dakika önce + + + {0:N0} dakika önce + + + {0:N0} ay önce + + + {0:N0} ay önce + + + {0:N0} saniye önce + + + {0:N0} saniye önce + + + {0:N0} yıl önce + + + {0:N0} yıl önce + + + Geçersiz kimlik doğrulama kodu + + + Yeni bir kimlik doğrulama kodu almak için kodu yeniden girmeyi veya yeniden gönder düğmesine tıklamayı deneyin. + + + Kimlik doğrulama kodu gönderildi! + + + Kimlik doğrulama kodunu almadıysanız support@github.com adresine başvurun. + + + Gözat + + + github.com ile bağlantı kurulamadı + + + Sunucuya bağlanılamadı. + + + Oluştur + + + Açıklama (İsteğe Bağlı) + + + Tarayıcıda Aç + + + İptal + + + Gist oluşturuldu + + + Gist oluşturulamadı + + + Sağlayan: + + + Gizlilik + + + Anonim kullanım verileri göndererek geliştirmemize yardımcı olun + + + Panoya kopyalanamadı. Lütfen yeniden deneyin. + + + Bağlantı panoya kopyalandı + + + Depo başarıyla oluşturuldu. + + + Özel Gist + + + Dosya Adı + + + {0} oturumunuz açılmadığından belirli git işlemleri başarısız olabilir. [Şimdi oturum açın]({1}) + + + Wiki + + + Pulse + + + Yol + + + Konular + + + Grafikler + + + GitHub'da Yayımla + + + Açık kaynak ve özel projeler için güçlü işbirliği, kod incelemesi ve kod yönetimi. + + + Bağlan... + + + Kopya + + + Doğrula + + + İki öğeli kimlik doğrulama + + + Kaydol + + + Oturumu kapat + + + Kodu kayıtlı SMS Cihazınıza yeniden gönderin + + + Yeniden Gönder + + + Depo Adı + + + Bu deponun uzak deposu yok. GitHub'da yayımlamak için formu doldurun. + + + Yayımla + + + veya + + + Kimlik doğrulama kodunuzu görüntülemek için cihazınızda iki öğeli kimlik doğrulama uygulamasını açın. + + + Depo yok + + + Ad + + + Özel Depo + + + Yerel yol: + + + Lisans + + + Daha fazla bilgi + + + Git ignore + + + Depolarda ara + + + Depoların bir kısmı veya tümü yüklenmemiş olabilir. İletişim kutusunu kapatın ve yeniden deneyin. + + + Depolar yüklenirken bir hata oluştu + + + GitHub Enterprise sunucusu adresi + + + Ana bilgisayar kullanılamıyor veya bir GitHub Enterprise sunucusu değil. Adresi denetleyin ve yeniden deneyin. + + + Kullanıcı adı veya e-posta + + + Parola + + + Oturum aç + + + Lütfen internet bağlantınızı kontrol edin ve yeniden deneyin. + + + GitHub Enterprise kullanmıyor musunuz? + + + Hesabınız yok mu? + + + Başlık (gerekli) + + + Açıklama + + + Bu depoyu GitHub'da yayımlayarak açık kaynak ve özel projeler için güçlü işbirliği, kod incelemesi ve kod yönetiminden yararlanın. + + + Bu depo GitHub'da değil + + + Depo yok + + + Burada bir git deposu bulunamadı. Çalışmaya başlamak için bir git projesi açın veya bir projede "Dosya -> Kaynak Denetimine Ekle" seçeneğine tıklayın. + + + Hesap oluştur + + + Dalları filtrele + + + GitHub'da Yayımla + + + Kullanmaya Başlayın + + + Oturum aç + + + Oturum aç... + + + Yerel dal güncel + + + Değişiklikler ({0}) + + + Değişiklikleri Görüntüle + + + Varsayılan Eylem Olarak Dosyayı Karşılaştır + + + Dosyayı Görüntüle + + + Varsayılan Eylem Olarak Dosyayı Aç + + + Liste Görünümüne Geç + + + Ağaç Görünümüne Geç + + + {0} güncelleştirildi + + + Çekme İsteğini GitHub'da Görüntüle + + + Visual Studio için GitHub'a hoş geldiniz! [Eğitimimize](show-training) veya [belgelerimize](show-docs) göz atmaya ne dersiniz? + +[Bunu bir daha gösterme](dont-show-again) + + + Güncelleştirildi + + + Düzenleyici kenar boşluğunda PR açıklamalarını göster + + + Deneysel özellikler + + + Bu özellikler gelecekteki bir sürümde değişebilir + + + Çözümdeki Değişiklikleri Görüntüle + + + Dosyayı Çözümde Aç + + + Belirteç + + + İncelemenize devam edin + + + İncelemenizi ekleyin + + + Gözden Geçirenler + + + İnceleme açıklaması ekle + + + Tek bir açıklama ekleyin + + + Çatal Oluştur + + + Hata Ayıklama + + + İzleme Günlüğünü Etkinleştir + + + GitHub uzantısı Blend içinde kullanılamıyor + + + Açıklamayı güncelleştir + + + İptal + + + Bekliyor + + + İnceleme başlat + + + Buraya bir açıklama eklemek için değişikliklerinizi commit işlemi yapıp göndermelisiniz. + + + Önceki Açıklama + + + Sonraki Açıklama + + + Çekme İsteğini Görüntüle, Oluştur veya Kullanıma Al + + + Geri + + + Depo Çatalı Oluştur + + + Depo çatalı oluştur + + + Yerel deponuzun şu öğesini güncelleştirin: + + + işaret edecek şekilde + + + Çekme isteği oluştur + + + GitHub'da oturum açın + + + Çek + + + Anında İlet + + + Eşitle + + + yazdı + + + Şuna yönelik incelemenizi gönderin: + + + İnceleme özetiniz + + + Yalnızca açıklama + + + Onayla + + + İstek değişiklikleri + + + Açıklamalar + + + Süresi geçen açıklamalar + + + Yeni Oluştur + + + Atanan + + + Yazar + + + Tarayıcınızla oturum açın + + + + + + Yazara Göre Filtrele + + + Çatal Seçin + + + Bu açıklamayı silmek istediğinizden emin misiniz? + + + Açıklamayı Sil + + + Yeniden dene + + + Açık çekme isteği yok + + + Çekme istekleri GitHub'da bir depoya gönderdiğiniz değişiklikleri başkalarına duyurmanıza olanak tanır + + + Aramanızla eşleşen sonuç yok. + + + Başlamak için şunları yapabilirsiniz: + + + çekme isteği oluştur + + + '{0}' konumundaki depo açılsın mı? + + + Hedef URL'nin geçerli depodan farklı bir sahibi var. + + + Gidilecek etkin bir depo yok + + + Çalışma dosyası '{0}' konumundaki dosyadan farklı. Lütfen karşılık gelen dalı, çekme isteğini veya commit'i kullanıma alın. + + + Lütfen '{0}' deposunu açın ve yeniden deneyin + + + '{0}' kaynağından açılamadı. Şu anda yalnızca depo dosyalarına bağlantı kuran URL'ler destekleniyor. + + + Panoda GitHub URL'si bulunamadı + + + Hedef URL geçerli depoda bulunamadı. Getirme işlemi gerçekleştirdikten sonra yeniden deneyin. + + + Hedef yoldaki dizin boş değil. + + + Bu konumda bir depo zaten var ancak bu deponun "origin" adlı bir uzak deposu yok. + + + Bu konumda bir depo zaten var ancak bu depo {0} adlı bir uzak depoya sahip. + + + Zaten bu konuma kopyaladınız. Yerel depoyu açmak için 'Aç' seçeneğine tıklayın. + + + Depo için GitHub URL'si bulunamıyor + + + GitHub URL'lerinin bulunması için depoların tanımlı bir "origin" adlı uzak depoya sahip olması gerekir. + + + Lütfen mevcut uzak depolarınızdan birini 'origin' olarak adlandırın veya 'origin' adlı yeni bir uzak depo ekleyip getirin. Bu işlemi komut satırından veya aşağıdaki düğmeye tıklayarak gerçekleştirebilirsiniz. + + + Depoda '{0}' öğesine karşılık gelen dosya bulunamadı. Lütfen bir 'git fetch' işlemi gerçekleştirin ya da hedef çekme isteğini kullanıma alın. + + + En son gönderilen {0} + + + Açıklama + + + Çekme isteğini kapat + + + Sorunu kapat + + + Kapat ve açıklama ekle + + + Yeniden aç ve açıklama ekle + + + Sorunu yeniden aç + + + Çekme isteğini yeniden aç + + + {0} işleme + + + birkaç işleme ekledi + + + ve diğer kişiler + + + Bu konuşma çözümlendi olarak işaretlendi + + + Depolarınız + + + Ortak çalışan depoları + + + Katkıda bulunulan depolar + + + Bir URL arayın veya girin + + + + Gözat... + + + Kopyalama + + + Hesap Ekle/Değiştir + + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.zh-CN.resx b/src/GitHub.Resources/Resources.zh-CN.resx index f1a1758f8d..386d48870c 100644 --- a/src/GitHub.Resources/Resources.zh-CN.resx +++ b/src/GitHub.Resources/Resources.zh-CN.resx @@ -59,46 +59,46 @@ : using a System.ComponentModel.TypeConverter : and then encoded with base64 encoding. --> - - + + - + - - - - + + + + - - + + - - + + - - - - + + + + - + - + @@ -120,41 +120,38 @@ 为新存储库选择包含文件夹。 - - 克隆存储库 - 从 GitHub 打开 - 无法连接到github.com + 无法连接到 github.com - 创建一个GitHub Gist + 创建 GitHub Gist - 创建一个{0}存储库 + 创建 {0} 存储库 GistFromVisualStudio.cs - 请输入企业网址 + 请输入 Enterprise URL - 请输入有效的企业网址 + 请输入有效的 Enterprise URL - 不是企业服务器。请输入企业服务器 URL + 不是 Enterprise 服务器。请输入 Enterprise URL - (是否忘记了您的密码?) + (忘记了密码?) - 请确保使用您的密码而不是登录的个人访问令牌。 + 请确保使用密码而非个人访问令牌进行登录。 - 检查您的用户名和密码然后重试 + 请检查用户名和密码,然后重试 登录失败。 @@ -163,103 +160,94 @@ 连接到 GitHub - 请输入您的密码 + 请输入密码 - 在 [{1}] ({2}) 上成功创建分支 ** {0} ** 拉取请求 - - - 发布版本库 - - - 发布版本库至 {0} + 已成功创建分支 **{0}** 的拉取请求,创建时间为 [{1}] ({2}) - 请输入请求的标题! + 请输入拉取请求标题 源和目标分支不能相同 - 远程不存在源分支, 你有没有推送它? - - - 没有选定的存储库。 + 源分支在远程不存在,是否已推送该分支? - 请输入名称 + 请输入存储库路径 - 请输入有效路径 + 请输入有效的路径 - 路径包含无效字符 + 路径包含无效的字符 - 路径太长 + 路径过长 - 版本库 '{0}/{1}'已存在。 + 存储库“{0}/{1}”已存在。 - 请更改存储库名称或选择其他帐户, 然后重试。 + 请更改存储库名称或选择其他帐户,然后重试。 - 私密文档 + 已超出专用存储库配额。 - 同名的广告活动已存在 + 此位置已存在具有相同名称的存储库 - 请输入姓名 + 请输入存储库名称 - 这个名字不能超过100个字符 + 存储库名称的长度必须少于 100 个字符。 - 将要创建为 {0} + 将创建为 {0} - 在你的手机上打开双重身份验证App查看验证码。 + 打开设备上的双因素身份验证应用以查看你的验证码。 - 我们将发送一条验证码短信到您的手机。 + 我们已通过短信向你发送了验证码。 - 双重身份认证 + 需要双因素身份验证 - 在此处输入身份验证代码中的符号 + 在此处输入验证码符号 - 请输入您的用户名或邮箱地址. + 请输入用户名或电子邮件地址 - 用户名或电子邮件地址不能有空格 + 用户名或电子邮件地址不能包含空格 拉取请求 - 推送请求 + 拉取请求 添加 - fork + 分支 [无效] - 你必须先拉取再推送 + 必须先拉取然后才能推送 - 没有需要拉取的提交 + 没有可拉取的提交 - 没有需要推送的提交 + 没有可推送的提交 * 未提供说明。* @@ -271,66 +259,67 @@ 签出到 {0} - 从 {0} 分支 {1} 拉取 + 从分支 {1} 拉取 {0} - 推至 {0} 分支 {1} + 推送到 {0} 分支 {1} - 远程 + 远程库 重命名 - 源存版本库不再可用。 + 源存储库不再可用。 - 无法签出, 因为您的工作目录有未提交的更改。 + 你的工作目录包含未提交的更改,因此无法签出。 同步 {0} 子模块 - 在PATH 找不到 Git.exe。 + 路径上找不到 Git.exe。 -请安装 Git for Windows : https://git-scm.com/download/win +请从以下位置安装适用于 Windows 的 Git: +https://git-scm.com/download/win - 批准 + 已批准 - 更改需求 + 已请求更改 - 已评论 + 已注释 - 进行中 + 正在进行 - 按回车键导航到编辑器 + 按 Enter 以导航到编辑器 - 在导航到编辑器之前签出 PR 分支 + 请先签出 PR 分支,然后再导航到编辑器 - 按回车键导航到编辑器 (PR 分支必须签出) + 按 Enter 以导航到编辑器(必须签出 PR 分支) - Fork存储库 + 分叉存储库 - 开关原点 + 切换原点 - 您确定要取消此评审吗?您将丢失所有待处理的评论。 + 确实要取消此评审吗? 将会丢失所有挂起的评论。 取消评审 - 有一个文件在目标路径已存在。 + 目标路径中存在一个文件。 需要注销 @@ -348,7 +337,7 @@ {0:N0} 小时前 - 就现在 + 此刻 {0:N0} 分钟前 @@ -357,10 +346,10 @@ {0:N0} 分钟前 - {0:N0} 月前 + {0:N0} 个月前 - {0:N0} 月前 + {0:N0} 个月前 {0:N0} 秒前 @@ -375,25 +364,25 @@ {0:N0} 年前 - 无效的验证码 + 验证码无效 - 尝试再次输入代码或单击重新发送按钮以获取新的验证码。 + 请尝试重新输入代码,或单击“重新发送”按钮以获取新的验证码。 - 验证码已发送! + 已发送验证码! - 如果您没有收到验证码,请联系 support@github.com 。 + 如果未收到验证码,请联系 support@github.com。 浏览 - 无法连接到github.com + 无法连接到 github.com - 无法连接服务器 + 无法连接到服务器。 创建 @@ -408,40 +397,40 @@ 取消 - Gist已创建 + 已创建 Gist - 创建Gist失败 + 未能创建 Gist - + 依据 隐私 - 通过发送匿名使用数据来帮助我们改进 + 请匿名发送使用情况数据,帮助我们不断改进 - 无法复制到剪贴板,请重试。 + 无法复制到剪贴板。请重试。 - 链接已经复制到剪贴板 + 复制到剪贴板的链接 - 版本库创建成功。 + 已成功创建存储库。 - 私有Gist + 专用 Gist 文件名 - 你没有登录到 {0},因此操作可能才会失败. [立即登录]({1}) + 你未登录到 {0},因此某些 git 操作可能会失败。[立即登录]({1}) - 维基 + Wiki 脉冲 @@ -453,13 +442,13 @@ 问题 - 图表 + 关系图 - 发布至GitHub + 发布到 GitHub - 让您的开源和私有项目拥有强大的协作、代码审查、以及代码管理。 + 用于开放源和私有项目的功能强大的协作、代码评审和代码管理。 连接... @@ -480,7 +469,7 @@ 注销 - 再次将验证码短信发送至您的注册手机中 + 再次将代码发送到你注册的 SMS 设备 重新发送 @@ -489,16 +478,16 @@ 存储库名称 - 这个存储库没有托管至远程。填写表单,将其发布到Github。 + 此存储库没有远程库。填写此表单以将其发布到 GitHub。 发布 - + 或者 - 在你的手机上打开双重身份验证App查看验证码。 + 打开设备上的双因素身份验证应用以查看你的验证码。 没有存储库 @@ -507,10 +496,10 @@ 名称 - 私有存储库 + 专用存储库 - 本地路径 + 本地路径: 许可证 @@ -519,22 +508,22 @@ 了解详细信息 - Git忽略 + Git 忽略 搜索存储库 - 部分或全部存储库没有加载,请关闭对话框重试。 + 可能未加载部分或所有存储库。请关闭对话框,然后重试。 - 加载存储库时发送意外。 + 加载存储库时出现错误 - GitHub企业服务器地址 + GitHub Enterprise 服务器地址 - 无效主机或不是GitHub企业服务器,请检查地址并重试。 + 主机不可用或不是 GitHub Enterprise 服务器。请检查地址,然后重试。 用户名或电子邮件 @@ -546,31 +535,31 @@ 登录 - 请检查你的 Internet 连接,然后重试。 + 请检查你的 Internet 连接,然后重试。 - 没有GitHub企业版? + 没有 GitHub Enterprise? 没有帐户? - 标题(必须) + 标题(必需) 描述 - 将此存储库发布到 GitHub, 并为开源和私有项目获得强大的协作、代码审阅和代码管理。 + 将此存储库发布到 GitHub 并获取用于开放源和私有项目的功能强大的协作、代码评审和代码管理。 - 此存储库未托管至Github + 此存储库不在 GitHub 上 - 没有存储库 + 未找到任何存储库 - 我们在这里找不到 git 存储库。打开一个 git 项目或单击项目中的 "文件"-> "添加到源代码管理" 以开始. + 此处找不到 git 存储库。请打开 git 项目或在项目中单击“文件”->“添加到源代码管理”以开始使用。 创建帐户 @@ -591,93 +580,90 @@ 登录... - 本地分支最新! + 最新的本地分支 - 变更:({0}) + 更改({0}) - 查看变更 + 查看更改 - 将比较文件作为默认操作 + 将文件比作默认操作 查看文件 - 将打开文件作为默认操作 + 将文件作为默认操作打开 切换到列表视图 - 切换到树视图 + 切换到树状视图 - 已更新“{0}” + 已更新 {0} - 在GitHub上查看拉取请求 + 查看 GitHub 上的拉取请求 - 欢迎使用 GitHub for Visual Studio! 您可以先看看这里 [培训](show-training) 或[文档](show-docs)? + 欢迎使用适用于 Visual Studio 的 GitHub! 何不看看我们的[培训](show-training)或[文档](show-docs)? [不再显示](dont-show-again) - 已更新 + 更新 - 在编辑器边缘显示PR注释 + 在编辑器边距上显示 PR 注释 实验性功能 - 我们将在未来的版本中改变此功能。 + 这些功能在未来版本中可能会更改 - 在解决方案中查看更新 + 查看解决方案中的更改 - 解决方案中打开文件 + 在解决方案中打开文件 令牌 - 继续复审 + 继续你的评审 - 添加复审 + 添加你的评审 - 复审人员 + 审阅者 - 添加复审注释 + 添加评审评论 - 添加评论 + 添加单个注释 - Fork + 分支 调试 - 启用日志跟踪 + 启用跟踪日志记录 - GitHub括在在Blend内部无效 - - - 在团队资源管理器中显示分叉按钮 + GitHub 扩展在 Blend 中不可用 - 更新注释 + 更新评论 取消 @@ -686,10 +672,10 @@ 挂起 - 开始复审 + 开始评审 - 您必须提交和推送更改, 才能在此添加注释。 + 必须提交并推送你的更改才能在此处添加评论。 上一个评论 @@ -701,25 +687,25 @@ 查看、签出或创建拉取请求 - 返回 + 上一步 - Fork存储库 + 分叉存储库 - Fork此存储库 + 创建存储库分支 更新本地存储库的 - 要指向 + 指向 创建拉取请求 - 登录GitHub + 登录到 GitHub 拉取 @@ -731,34 +717,34 @@ 同步 - 已写 + 已写入 - 提交你的批注 + 提交评审 - 你的批注概要 + 评审摘要 - 仅批注 + 仅注释 批准 - 请求变更 + 请求更改 - 批注 + 评论 - 过期批注 + 过时的注释 - 创建 + 新建 - 分配 + 代理人 作者 @@ -770,69 +756,139 @@ 打开 - 按作者过滤 + 按作者筛选 - 选择Fork + 选择分支 - 您确定要删除此注释吗? + 是否确实要删除此评论? - 删除注释 + 删除评论 重试 - 这里没有任何打开的推送请求 + 没有任何开放的拉取请求 - 通过推送请求告诉其他人关于你推送到一个GitHub存储库的变更 + 通过拉取请求,可告知其他人发布到 GitHub 上的存储库的更改 - 您的搜索没有匹配结果 + 没有与搜索匹配的结果。 - 现在开始你可以 + 若要开始,可以 创建拉取请求 - 在“{0}”打开存储库? + 要打开 {0} 中的存储库? - 目标URL与当前存储库拥有者不同。 + 目标 URL 的所有者与当前存储库的所有者不同。 - 没有活动的存储库可以导航 + 没有可导航的任何活动存储库 - 自创建固定链接以来,此文件已更改 + 工作文件不同于 {0} 处的文件。请签出相应的分支、拉取请求或提交。 - 请打开存储库“{0}”,然后重试 + 请打开存储库 {0},然后重试 - 无法从“{0}”打开。目前仅支持链接到存储库文件的URL。 + 无法从 {0} 打开。当前仅支持链接到存储库文件的 URL。 - 无法在剪贴板中找到GitHub网址! + 在剪贴板中找不到 GitHub URL - 无法在当前存储库中找到目标URL。在进行提取后再试一次。 + 当前存储库中找不到目标 URL。请执行提取后重试。 - - 此位置已经有一个目录, 但它不包含存储库。 + + 目标路径中的目录不为空。 - 此位置已存在存储库, 但它没有名为 "origin"的远端。 + 此位置已存在一个存储库,但该存储库不具有名为 "origin" 的远程库。 - 此位置已存在存储库, 但它具有 {0} 的远端。 + 此位置已存在一个存储库,但该存储库具有名为 {0} 的远程库。 - 您已经克隆到此位置。单击 "打开" 打开本地存储库。 + 已克隆到此位置。单击“打开”打开本地存储库。 + + + 找不到存储库的 GitHub URL + + + 存储库必须定义名为 "origin" 的远程库才能找到其 GitHub URL。 + + + 请将其中一个现有远程库重命名为 "origin" 或添加一个名为 "origin" 的新远程库并提取。可从命令行或通过单击下面的按钮完成此操作。 + + + 在存储库中找不到对应于 {0} 的文件。请执行 "git fetch" 或签出目标拉取请求。 + + + 最近已推送 {0} + + + 注释 + + + 关闭拉取请求 + + + 关闭问题 + + + 关闭并注释 + + + 重新打开并注释 + + + 重新打开问题 + + + 重新打开拉取请求 + + + {0} 个提交 + + + 已添加部分提交 + + + 以及其他 + + + 此对话标记为已解决 + + + 你的存储库 + + + 协作者存储库 + + + 已参与存储库 + + + 搜索或输入 URL + + + + 浏览... + + + 克隆 + + + 添加/更改帐户 - + \ No newline at end of file diff --git a/src/GitHub.Resources/Resources.zh-TW.resx b/src/GitHub.Resources/Resources.zh-TW.resx new file mode 100644 index 0000000000..4a2f52e11b --- /dev/null +++ b/src/GitHub.Resources/Resources.zh-TW.resx @@ -0,0 +1,894 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 為新的存放庫選取包含資料夾。 + + + 從 GitHub 打開 + + + 無法連線到 github.com + + + 建立 GitHub Gist + + + 創建 {0} 存儲庫 + + + GistFromVisualStudio.cs + + + 請輸入 Enterprise URL + + + 請輸入有效的 Enterprise URL + + + 非 Enterprise 伺服器。請輸入 Enterprise URL + + + (忘記密碼?) + + + 請務必使用您的密碼,而非個人存取權杖來登入。 + + + 請檢查您的使用者名稱及密碼,然後再試一次 + + + 登入失敗。 + + + 連線到 GitHub + + + 請輸入您的密碼 + + + 已成功於 [{1}]({2}) 建立分支 **{0}** 的提取要求 + + + 請輸入提取要求的標題 + + + 來源及目標分支不可相同 + + + 遠端不存在來源分支,您有推送它嗎? + + + 請輸入存放庫路徑 + + + 請輸入有效的路徑 + + + 路徑包含無效字元 + + + 路徑太長 + + + 存放庫 '{0}/{1}' 已存在。 + + + 變更存放庫名稱或選取其他帳戶,然後再試一次。 + + + 已超過私人的存放庫配額。 + + + 此位置已存在同名的存放庫 + + + 請輸入存放庫名稱 + + + 存放庫名稱必須少於 100 個字元 + + + 將會建立為 {0} + + + 開啟您裝置上的雙重要素驗證應用程式,即可檢視驗證碼。 + + + 我們會透過簡訊傳送內含驗證碼的訊息給您。 + + + 需要雙重要素驗證 + + + 在此處輸入登入驗證碼 + + + 請輸入您的使用者名稱或電子郵件地址 + + + 使用者名稱或電子郵件地址不得有空格 + + + 提取要求 + + + 提取要求 + + + 新增 + + + 分支 + + + [無效] + + + 您必須先提取才能推送 + + + 沒有任何可供提取的認可 + + + 沒有任何可供推送的認可 + + + *未提供任何描述。* + + + 簽出 {0} + + + 簽出 {0} + + + 從 {0} 分支 {1} 提取 + + + 推送至 {0} 分支 {1} + + + 遠端存放庫 + + + 重新命名 + + + 來源存放庫已無法再使用。 + + + 因為您的工作目錄具有未認可的變更,所以無法簽出。 + + + 同步 {0} 子模組 + + + 在路徑上找不到 Git.exe。 + +請從以下網址安裝 Git for Windows: +https://git-scm.com/download/win + + + 已核准 + + + 已要求變更 + + + 已加上註解 + + + InProgress + + + 按 ENTER 可瀏覽至 [編輯器] + + + 在巡覽至 [編輯器] 前,簽出 PR 分支 + + + 按 ENTER 可瀏覽至 [編輯器] (必須簽出 PR 分支) + + + 分支存放庫 + + + 切換原點 + + + 確定要取消此檢閱嗎? 您將失去所有暫止的註解。 + + + 取消檢閱 + + + 檔案存在於目的地路徑。 + + + 必須登出 + + + {0:N0} 天前 + + + {0:N0} 天前 + + + {0:N0} 小時前 + + + {0:N0} 小時前 + + + 現在 + + + {0:N0} 分鐘前 + + + {0:N0} 分鐘前 + + + {0:N0} 個月前 + + + {0:N0} 個月前 + + + {0:N0} 秒前 + + + {0:N0} 秒前 + + + {0:N0} 年前 + + + {0:N0} 年前 + + + 驗證碼無效 + + + 請嘗試再次輸入驗證碼,或按一下 [重新傳送] 按鈕取得新的驗證碼。 + + + 已傳送驗證碼! + + + 若您無法收到驗證碼,請連絡 support@github.com。 + + + 瀏覽 + + + 無法連線到 github.com + + + 無法連線到伺服器。 + + + 建立 + + + 描述 (選擇性) + + + 以瀏覽器開啟 + + + 取消 + + + 已建立 Gist + + + 無法建立 Gist + + + 透過 + + + 隱私權 + + + 傳送匿名使用方式資料來協助我們改善 + + + 無法複製到剪貼簿。請再試一次。 + + + 複製到剪貼簿的連結 + + + 已成功建立存放庫。 + + + 私人 Gist + + + 檔案名稱 + + + 您未登入 {0},因此特定的 git 作業可能會失敗。[立即登入]({1}) + + + Wiki + + + 動態 + + + 路徑 + + + 問題 + + + 圖形 + + + 發佈至 GitHub + + + 為開放原始碼和私人專案提供之強大的共同作業、程式碼檢閱和程式碼管理。 + + + 連線... + + + 複製 + + + 驗證 + + + 雙重要素驗證 + + + 註冊 + + + 登出 + + + 再次傳送代碼到您已註冊的 SMS 裝置 + + + 重送 + + + 儲存機制名稱 + + + 此存放庫沒有遠端存放庫。請填寫表單以將其發佈至 GitHub。 + + + 發行 + + + + + + 開啟您裝置上的雙重要素驗證應用程式,即可檢視驗證碼。 + + + 沒有任何存放庫 + + + 名稱 + + + 私人存放庫 + + + 本機路徑: + + + 授權 + + + 進一步了解 + + + Git 忽略 + + + 搜尋存放庫 + + + 可能未載入部分或所有存放庫。請關閉對話方塊,並再試一次。 + + + 載入存放庫時發生錯誤 + + + GitHub Enterprise 伺服器位址 + + + 主機無法使用,或其不是 GitHub Enterprise 伺服器。請檢查位址,並再試一次。 + + + 使用者名稱或電子郵件 + + + 密碼 + + + 登入 + + + 請檢查您的網際網路連線,並再試一次。 + + + 沒有 GitHub Enterprise 嗎? + + + 沒有帳戶嗎? + + + 標題 (必要項) + + + 描述 + + + 將此存放庫發佈至 GitHub,為開放原始碼和私人專案取得強大的共同作業、程式碼檢閱和程式碼管理。 + + + 此存放庫不在 GitHub 上 + + + 沒有任何存放庫 + + + 這裡找不到 git 存放庫。請開啟 git 專案,或按一下專案中的 [檔案] -> [新增到原始檔控制],以便開始使用。 + + + 建立帳戶 + + + 篩選分支 + + + 發佈至 GitHub + + + 開始使用 + + + 登入 + + + 登入... + + + 最新的本機分支 + + + 變更 ({0}) + + + 檢視變更 + + + 以比較檔案作為預設動作 + + + 檢視檔案 + + + 開啟檔案作為預設動作 + + + 切換至清單檢視 + + + 切換至樹狀檢視 + + + 已更新 {0} + + + 檢視 GitHub 上的提取要求 + + + 歡迎使用適用於 Visual Studio 的 GitHub! 何不來看看我們的 [訓練](顯示訓練) 或 [文件](顯示文件)? + +[不要再顯示此項目](不要再顯示) + + + 已更新 + + + 在編輯器邊界上顯示 PR 註解 + + + 實驗性功能 + + + 這些功能在未來的版本中可能會變更 + + + 檢視解決方案中的變更 + + + 在解決方案中開啟檔案 + + + 語彙基元 + + + 繼續您的檢閱 + + + 新增您的檢閱 + + + 檢閱者 + + + 新增檢閱註解 + + + 新增單一註解 + + + 分支 + + + 偵錯 + + + 啟用追蹤記錄 + + + 無法在 Blend 內使用 GitHub 延伸模組 + + + 更新註解 + + + 取消 + + + 暫止 + + + 開始檢閱 + + + 您必須認可並推送變更,以在這裡新增註解。 + + + 上一個註解 + + + 下一個註解 + + + 檢視、簽出或建立提取要求 + + + 上一步 + + + 分支存放庫 + + + 派生存放庫 + + + 更新您本機存放庫的 + + + 指向 + + + 建立提取要求 + + + 登入 GitHub + + + 提取 + + + 推送 + + + 同步 + + + 已寫入 + + + 提交您的檢閱 + + + 您的檢閱摘要 + + + 僅限註解 + + + 核准 + + + 要求變更 + + + 註解 + + + 過期的註解 + + + 建立新的 + + + 受託人 + + + 作者 + + + 使用您的瀏覽器登入 + + + 開啟 + + + 依作者篩選 + + + 選取分支 + + + 您確定要刪除這個註解嗎? + + + 刪除註解 + + + 重試 + + + 沒有任何未處理的提取要求 + + + 提取要求可讓您告訴其他人您推送至 GitHub 上存放庫的變更 + + + 沒有任何結果符合您的搜尋。 + + + 若要開始使用,您可以 + + + 建立提取要求 + + + 要在 '{0}' 開啟存放庫嗎? + + + 目標 URL 與目前存放庫的擁有者不同。 + + + 沒有任何使用中的存放庫可供瀏覽 + + + 工作檔案與 '{0}' 中的檔案不同。請簽出對應的分支、提取要求或認可。 + + + 請開啟存放庫 '{0}',並再試一次 + + + 無法從 '{0}' 開啟。目前僅支援連結到存放庫檔案的 URL。 + + + 在剪貼簿中找不到 GitHub URL + + + 在目前存放庫中找不到目標 URL。請在執行擷取後再試一次。 + + + 在目的地路徑中的目錄並非空白。 + + + 存放庫已存在於此位置,但其不具有名為 "origin" 的遠端存放庫。 + + + 存放庫已存在於此位置,但其具有 {0} 的遠端存放庫。 + + + 您已複製到此位置。按一下 [開啟] 可開啟本機存放庫。 + + + 找不到存放庫的 GitHub URL + + + 存放庫必須定義名為 "origin" 的遠端存放庫,才能尋找其 GitHub URL。 + + + 請將其中一個現有的遠端存放庫重新命名為 'origin',或新增名為 'origin' 的遠端並擷取。此作業可從命令列或按一下下方的按鈕完成。 + + + 找不到存放庫中對應 '{0}' 的檔案。請執行 'git fetch' 或簽出目標提取要求。 + + + {0} 最近一次推送 + + + 註解 + + + 關閉提取要求 + + + 關閉問題 + + + 關閉並加上註解 + + + 重新開啟並加上註解 + + + 重新開啟問題 + + + 重新開啟提取要求 + + + {0} 項認可 + + + 已新增一些認可 + + + 及其他 + + + 此交談已標示為已解決 + + + 您的存放庫 + + + 共同作業者存放庫 + + + 已提供給存放庫 + + + 搜尋或輸入 URL + + + + 瀏覽... + + + 複製 + + + 新增/變更帳戶 + + \ No newline at end of file diff --git a/src/GitHub.Services.Vssdk/Commands/VsCommandBase.cs b/src/GitHub.Services.Vssdk/Commands/VsCommandBase.cs index cca02d90e8..ff8c8ecc38 100644 --- a/src/GitHub.Services.Vssdk/Commands/VsCommandBase.cs +++ b/src/GitHub.Services.Vssdk/Commands/VsCommandBase.cs @@ -4,6 +4,8 @@ using GitHub.Commands; using Microsoft.VisualStudio.Shell; +#pragma warning disable CA1033 // Interface methods should be callable by child types + namespace GitHub.Services.Vssdk.Commands { /// diff --git a/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj b/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj index d846a0cf29..64dd70a510 100644 --- a/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj +++ b/src/GitHub.Services.Vssdk/GitHub.Services.Vssdk.csproj @@ -15,4 +15,10 @@ + + + + + + diff --git a/src/GitHub.Services.Vssdk/Services/TippingService.cs b/src/GitHub.Services.Vssdk/Services/TippingService.cs new file mode 100644 index 0000000000..e743f0d241 --- /dev/null +++ b/src/GitHub.Services.Vssdk/Services/TippingService.cs @@ -0,0 +1,89 @@ +using System; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Windows; +using GitHub.Logging; +using Microsoft; +using Serilog; +using IServiceProvider = System.IServiceProvider; + +namespace GitHub.Services.Vssdk.Services +{ + /// + /// This service is a thin wrapper around . + /// + /// + /// The interface is public, but contained within the 'Microsoft.VisualStudio.Shell.UI.Internal' assembly. + /// To avoid a direct dependency on 'Microsoft.VisualStudio.Shell.UI.Internal', we use reflection to call this service. + /// + public class TippingService : ITippingService + { + static readonly ILogger log = LogManager.ForContext(); + + // This is the only supported ClientId + public static readonly Guid ClientId = new Guid("D5D3B674-05BB-4942-B8EC-C3D13B5BD6EE"); + public static readonly Guid IVsTippingServiceGuid = new Guid("756F1DC9-47FA-42C5-9C06-252B54148EB8"); + + readonly IServiceProvider serviceProvider; + + public TippingService(IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider; + } + + /// + public void RequestCalloutDisplay(Guid calloutId, string title, string message, + bool isPermanentlyDismissible, FrameworkElement targetElement, + Guid vsCommandGroupId, uint vsCommandId) + { + var screenPoint = !Splat.ModeDetector.InUnitTestRunner() ? + targetElement.PointToScreen(new Point(targetElement.ActualWidth / 2, 0)) : default; + var point = new Microsoft.VisualStudio.OLE.Interop.POINT { x = (int)screenPoint.X, y = (int)screenPoint.Y }; + RequestCalloutDisplay(ClientId, calloutId, title, message, isPermanentlyDismissible, + point, vsCommandGroupId, vsCommandId); + } + + // Available on Visual Studio 2015 + void RequestCalloutDisplay(Guid clientId, Guid calloutId, string title, string message, bool isPermanentlyDismissible, + Microsoft.VisualStudio.OLE.Interop.POINT anchor, Guid vsCommandGroupId, uint vsCommandId) + { + var tippingService = serviceProvider.GetService(typeof(SVsTippingService)); + if (tippingService == null) + { + log.Error("Can't find {ServiceType}", typeof(SVsTippingService)); + return; + } + + Assumes.Present(tippingService); + var parameterTypes = new Type[] { typeof(Guid), typeof(Guid), typeof(string), typeof(string), typeof(bool), + typeof(Microsoft.VisualStudio.OLE.Interop.POINT), typeof(Guid), typeof(uint) }; + var tippingServiceType = tippingService.GetType(); + var method = tippingServiceType.GetInterfaces() + .FirstOrDefault(i => i.GUID == IVsTippingServiceGuid)?.GetMethod("RequestCalloutDisplay", + BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly, + null, parameterTypes, null); + if (method == null) + { + log.Error("Couldn't find method on {Type} with parameters {Parameters}", tippingServiceType, parameterTypes); + return; + } + + var arguments = new object[] { clientId, calloutId, title, message, isPermanentlyDismissible, anchor, + vsCommandGroupId, vsCommandId }; + method.Invoke(tippingService, arguments); + } + } + +#pragma warning disable CA1715 // Identifiers should have correct prefix +#pragma warning disable CA1040 // Avoid empty interfaces + [Guid("DCCC6A2B-F300-4DA1-92E1-8BF4A5BCA795")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + [TypeIdentifier] + [ComImport] + public interface SVsTippingService + { + } +#pragma warning restore CA1040 // Avoid empty interfaces +#pragma warning restore CA1715 // Identifiers should have correct prefix +} diff --git a/src/GitHub.StartPage/GitHub.StartPage.csproj b/src/GitHub.StartPage/GitHub.StartPage.csproj index 9000b81688..e3a4648d99 100644 --- a/src/GitHub.StartPage/GitHub.StartPage.csproj +++ b/src/GitHub.StartPage/GitHub.StartPage.csproj @@ -1,6 +1,5 @@  - $(VisualStudioVersion) @@ -30,8 +29,6 @@ true true true - ..\common\GitHubVS.ruleset - true true False False @@ -46,36 +43,22 @@ false bin\Debug\ - - true - full - false - TRACE;DEBUG;CODE_ANALYSIS - prompt - 4 - true - bin\Debug\ - true TRACE prompt 4 - true + false bin\Release\ Properties\SolutionInfo.cs + - - - Designer - - {e4ed0537-d1d9-44b6-9212-3096d7c3f7a1} @@ -101,95 +84,7 @@ ..\..\lib\15.0\Microsoft.TeamFoundation.Git.Controls.dll - - ..\..\packages\Microsoft.VisualStudio.CoreUtility.15.0.25901-RC\lib\net45\Microsoft.VisualStudio.CoreUtility.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Imaging.15.0.25901-RC\lib\net45\Microsoft.VisualStudio.Imaging.dll - True - - - ..\..\packages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.15.0.15.0.25901-RC\lib\Microsoft.VisualStudio.Shell.15.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Framework.15.0.25901-RC\lib\net45\Microsoft.VisualStudio.Shell.Framework.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Immutable.10.0.10.0.30319\lib\net40\Microsoft.VisualStudio.Shell.Immutable.10.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll - True - - - True - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll - True - - - True - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll - True - - - ..\..\packages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Threading.15.0.20-pre\lib\net45\Microsoft.VisualStudio.Threading.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Utilities.15.0.25901-RC\lib\net45\Microsoft.VisualStudio.Utilities.dll - True - - - ..\..\packages\Microsoft.VisualStudio.Validation.15.0.11-pre\lib\net45\Microsoft.VisualStudio.Validation.dll - True - - - ..\..\packages\Serilog.2.5.0\lib\net46\Serilog.dll - True - - - ..\..\packages\System.Reactive.4.0.0\lib\net46\System.Reactive.dll - - - ..\..\packages\System.Reactive.Core.3.1.1\lib\net46\System.Reactive.Core.dll - - - ..\..\packages\System.Reactive.Interfaces.3.1.1\lib\net45\System.Reactive.Interfaces.dll - - - ..\..\packages\System.Reactive.Linq.3.1.1\lib\net46\System.Reactive.Linq.dll - - - ..\..\packages\System.Reactive.PlatformServices.3.1.1\lib\net46\System.Reactive.PlatformServices.dll - - - ..\..\packages\System.Reactive.Windows.Threading.3.1.1\lib\net45\System.Reactive.Windows.Threading.dll - @@ -198,22 +93,29 @@ - + + 15.8.75-pre + + + 15.8.33 + + + 15.8.3252 + runtime; build; native; contentfiles; analyzers + all + + + 2.5.0 + + + 0.12.0 + + + 4.0.0 + - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs b/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs deleted file mode 100644 index d101666b7f..0000000000 --- a/src/GitHub.UI.Reactive/Controls/FilteredComboBox.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Reactive.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using System.Windows.Input; -using GitHub.Extensions.Reactive; -using ReactiveUI; - -namespace GitHub.UI -{ - public class FilteredComboBox : ComboBox - { - TextBox filterTextBox; - - public FilteredComboBox() - { - IsTextSearchEnabled = true; - - this.WhenAny(x => x.FilterText, x => x.Value) - .WhereNotNull() - .Throttle(TimeSpan.FromSeconds(0.2), RxApp.MainThreadScheduler) - .Subscribe(filterText => - { - Items.Filter += DoesItemStartWith(filterText); - }); - - this.WhenAny(x => x.FilterText, x => x.Value) - .Where(x => x == null) - .ObserveOn(RxApp.MainThreadScheduler) - .Subscribe(filterText => - { - Items.Filter += x => true; - }); - - this.WhenAny(x => x.FilterText, x => x.Value) - .Select(x => !string.IsNullOrEmpty(x)) - .Subscribe(isFiltered => - { - IsListFiltered = isFiltered; - }); - } - - public static readonly DependencyProperty FilterTextProperty = DependencyProperty.Register("FilterText", typeof(string), typeof(FilteredComboBox)); - - public string FilterText - { - get { return (string)GetValue(FilterTextProperty); } - set { SetValue(FilterTextProperty, value); } - } - - public static readonly DependencyProperty IsListFilteredProperty = DependencyProperty.Register("IsListFiltered", typeof(bool), typeof(FilteredComboBox)); - - public bool IsListFiltered - { - get { return (bool)GetValue(IsListFilteredProperty); } - set { SetValue(IsListFilteredProperty, value); } - } - - public override void OnApplyTemplate() - { - filterTextBox = GetTemplateChild("PART_FilterTextBox") as TextBox; - var popUp = GetTemplateChild("PART_Popup") as Popup; - if (popUp != null) - { - popUp.CustomPopupPlacementCallback = PlacePopup; - } - base.OnApplyTemplate(); - } - - /// - /// Override selection changed, because when the ItemSource is filtered - /// selection change is triggered, and the old selection is lost. - /// This allows us to remember the previous selection when no new selection has been made - /// and prevent a selection of null, when we had a previous selection. - /// - /// The selection changed arguments - protected override void OnSelectionChanged(SelectionChangedEventArgs e) - { - var hasOldSelection = e.RemovedItems != null && e.RemovedItems.Count == 1; - var hasNewSelectedItem = e.AddedItems != null && e.AddedItems.Count == 1; - - if (hasOldSelection && !hasNewSelectedItem) - { - return; - } - base.OnSelectionChanged(e); - } - - protected override void OnKeyDown(KeyEventArgs e) - { - if (filterTextBox != null && char.IsLetterOrDigit((char)e.Key)) - { - filterTextBox.Focus(); - Keyboard.Focus(filterTextBox); - } - else if (e.Key == Key.Enter && IsListFiltered && Items.Count > 0) - { - SelectedItem = Items.GetItemAt(0); - IsDropDownOpen = false; - e.Handled = true; - } - else - { - base.OnKeyDown(e); - } - } - - protected override void OnDropDownOpened(EventArgs e) - { - FilterText = ""; - SelectedIndex = -1; - base.OnDropDownOpened(e); - } - - Predicate DoesItemStartWith(string filterText) - { - return item => - { - var text = TryGetSearch(item); - - var comparison = IsTextSearchCaseSensitive - ? StringComparison.Ordinal - : StringComparison.OrdinalIgnoreCase; - - return text.StartsWith(filterText, comparison); - }; - } - - public CustomPopupPlacement[] PlacePopup(Size popupSize, Size targetSize, Point offset) - { - return new[] { new CustomPopupPlacement(new Point(0, targetSize.Height), PopupPrimaryAxis.Vertical) }; - } - - string TryGetSearch(object item) - { - string text = string.Empty; - var textPath = TextSearch.GetTextPath(this); - var propertyInfo = item.GetType().GetProperty(textPath); - if (propertyInfo != null) - { - text = propertyInfo.GetValue(item).ToString(); - } - return text; - } - } -} diff --git a/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml b/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml index 62b47d1114..246df5aa86 100644 --- a/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml +++ b/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml @@ -11,14 +11,21 @@ d:DesignWidth="262" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.TwoFactorAuthenticatonInputCustom}"> - + + + + + + + + diff --git a/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml.cs b/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml.cs index 099322afdf..396d09abc1 100644 --- a/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml.cs +++ b/src/GitHub.UI.Reactive/Controls/TwoFactorInput.xaml.cs @@ -42,7 +42,7 @@ public TwoFactorInput() six }; - foreach(var textBox in TextBoxes) + foreach (var textBox in TextBoxes) { SetupTextBox(textBox); } @@ -81,7 +81,7 @@ void SetText(string text) var digits = text.Where(Char.IsDigit).ToList(); for (int i = 0; i < Math.Min(6, digits.Count); i++) { - TextBoxes[i].Text = digits[i].ToString(); + TextBoxes[i].Text = digits[i].ToString(CultureInfo.InvariantCulture); } SetValue(TextProperty, String.Join("", digits)); } @@ -151,7 +151,7 @@ private void SetupTextBox(TextBox textBox) textBox.SelectAll(); } }; - + textBox.TextChanged += (sender, args) => { SetValue(TextProperty, String.Join("", GetTwoFactorCode())); @@ -170,7 +170,7 @@ bool MovePrevious() return MoveFocus(FocusNavigationDirection.Previous); } - bool MoveFocus(FocusNavigationDirection navigationDirection) + static bool MoveFocus(FocusNavigationDirection navigationDirection) { var traversalRequest = new TraversalRequest(navigationDirection); var keyboardFocus = Keyboard.FocusedElement as UIElement; diff --git a/src/GitHub.UI.Reactive/Controls/Validation/UserErrorMessages.cs b/src/GitHub.UI.Reactive/Controls/Validation/UserErrorMessages.cs index 6b0ea41385..6a18dc42ff 100644 --- a/src/GitHub.UI.Reactive/Controls/Validation/UserErrorMessages.cs +++ b/src/GitHub.UI.Reactive/Controls/Validation/UserErrorMessages.cs @@ -1,11 +1,9 @@ using System; using System.Diagnostics.CodeAnalysis; -using System.Reactive.Disposables; using System.Reactive.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; -using GitHub.Extensions; using GitHub.Extensions.Reactive; using ReactiveUI; using ReactiveUI.Legacy; @@ -14,7 +12,10 @@ namespace GitHub.UI { public class UserErrorMessages : UserControl { + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] readonly IDisposable whenAnyShowingMessage; + + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields")] readonly IDisposable whenAnyDataContext; public UserErrorMessages() @@ -34,7 +35,7 @@ public UserErrorMessages() }); } - public static readonly DependencyProperty IconMarginProperty = DependencyProperty.Register("IconMargin", typeof(Thickness), typeof(UserErrorMessages), new PropertyMetadata(new Thickness(0,0,8,0))); + public static readonly DependencyProperty IconMarginProperty = DependencyProperty.Register("IconMargin", typeof(Thickness), typeof(UserErrorMessages), new PropertyMetadata(new Thickness(0, 0, 8, 0))); public Thickness IconMargin { get { return (Thickness)GetValue(IconMarginProperty); } diff --git a/src/GitHub.UI.Reactive/GlobalSuppressions.cs b/src/GitHub.UI.Reactive/GlobalSuppressions.cs deleted file mode 100644 index 5f57521f46..0000000000 Binary files a/src/GitHub.UI.Reactive/GlobalSuppressions.cs and /dev/null differ diff --git a/src/GitHub.UI.Reactive/Validation/ReactiveValidatableObject.cs b/src/GitHub.UI.Reactive/Validation/ReactiveValidatableObject.cs index 5b52a4cf23..3a87fabe60 100644 --- a/src/GitHub.UI.Reactive/Validation/ReactiveValidatableObject.cs +++ b/src/GitHub.UI.Reactive/Validation/ReactiveValidatableObject.cs @@ -11,6 +11,8 @@ using GitHub.Services; using GitHub.Extensions; +#pragma warning disable CA1018 // Mark attributes with AttributeUsageAttribute + namespace GitHub.Validation { public class ReactiveValidatableObject : ReactiveObject, IDataErrorInfo diff --git a/src/GitHub.UI/Assets/Buttons.xaml b/src/GitHub.UI/Assets/Buttons.xaml deleted file mode 100644 index dd442160d7..0000000000 --- a/src/GitHub.UI/Assets/Buttons.xaml +++ /dev/null @@ -1,124 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/GitHub.UI/Assets/Controls.xaml b/src/GitHub.UI/Assets/Controls.xaml index 4e0abcc18b..0f7c550f0f 100644 --- a/src/GitHub.UI/Assets/Controls.xaml +++ b/src/GitHub.UI/Assets/Controls.xaml @@ -6,12 +6,8 @@ mc:Ignorable="d"> - - - - + - @@ -21,8 +17,8 @@ + + + + + \ No newline at end of file diff --git a/src/GitHub.UI/Assets/Controls/LightListBox.xaml b/src/GitHub.UI/Assets/Controls/LightListBox.xaml deleted file mode 100644 index 0e532890d5..0000000000 --- a/src/GitHub.UI/Assets/Controls/LightListBox.xaml +++ /dev/null @@ -1,99 +0,0 @@ - - - - - \ No newline at end of file diff --git a/src/GitHub.UI/Assets/Controls/RoundedCheckBox.xaml b/src/GitHub.UI/Assets/Controls/RoundedCheckBox.xaml deleted file mode 100644 index daf534e8a3..0000000000 --- a/src/GitHub.UI/Assets/Controls/RoundedCheckBox.xaml +++ /dev/null @@ -1,328 +0,0 @@ - - - - M 12,4.5 7,9.5 5,7.5 3.5,9 7,12.5 13.5,6 z - - - - - - - - - - - \ No newline at end of file diff --git a/src/GitHub.UI/Assets/Controls/ScrollViewerWithShadow.xaml b/src/GitHub.UI/Assets/Controls/ScrollViewerWithShadow.xaml index 695c731a70..7972420853 100644 --- a/src/GitHub.UI/Assets/Controls/ScrollViewerWithShadow.xaml +++ b/src/GitHub.UI/Assets/Controls/ScrollViewerWithShadow.xaml @@ -222,118 +222,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/GitHub.UI/Controls/AutoCompleteBox/AutoCompleteBox.cs new file mode 100644 index 0000000000..013c3e40e9 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/AutoCompleteBox.cs @@ -0,0 +1,1616 @@ +// (c) Copyright Microsoft Corporation. +// (c) Copyright GitHub, Inc. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. +// All other rights reserved. + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; +using System.Reactive; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Windows; +using System.Windows.Automation.Peers; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using GitHub.Extensions; +using GitHub.Helpers; +using GitHub.Models; +using GitHub.Services; +using GitHub.UI.Controls; +using GitHub.UI.Controls.AutoCompleteBox; +using GitHub.UI.Helpers; +using ReactiveUI; +using Control = System.Windows.Controls.Control; +using KeyEventArgs = System.Windows.Input.KeyEventArgs; + +namespace GitHub.UI +{ + /// + /// Represents a control that provides a text box for user input and a + /// drop-down that contains possible matches based on the input in the text + /// box. + /// + [SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", + Justification = "It's a control. It'll be disposed when the app shuts down.")] + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", + Justification = "Large implementation keeps the components contained.")] + [ContentProperty("ItemsSource")] + public class AutoCompleteBox : Control, IUpdateVisualState, IPopupTarget + { + private const string elementSelector = "Selector"; + private const string elementPopup = "Popup"; + private const string elementTextBoxStyle = "TextBoxStyle"; + private const string elementItemContainerStyle = "ItemContainerStyle"; + + private readonly IDictionary eventSubscriptions = new Dictionary(); + private List suggestions; // local cached copy of the items data. + + /// + // Gets or sets the observable collection that contains references to + // all of the items in the generated view of data that is provided to + /// the selection-style control adapter. + /// + private ObservableCollection view; + + /// + /// Gets or sets a value to ignore a number of pending change handlers. + /// The value is decremented after each use. This is used to reset the + /// value of properties without performing any of the actions in their + /// change handlers. + /// + /// The int is important as a value because the TextBox + /// TextChanged event does not immediately fire, and this will allow for + /// nested property changes to be ignored. + private int ignoreTextPropertyChange; + private bool ignorePropertyChange; // indicates whether to ignore calling pending change handlers. + private bool userCalledPopulate; // indicates whether the user initiated the current populate call. + private bool popupHasOpened; // A value indicating whether the popup has been opened at least once. + // Helper that provides all of the standard interaction functionality. Making it internal for subclass access. + internal InteractionHelper Interaction { get; set; } + // BindingEvaluator that provides updated string values from a single binding. + /// A weak event listener for the collection changed event. + private WeakEventListener collectionChangedWeakEventListener; + bool supportsShortcutOriginalValue; // Used to save whether the text input allows shortcuts or not. + readonly Subject populatingSubject = new Subject(); + readonly IDpiManager dpiManager; + + /// + /// Initializes a new instance of the + /// class. + /// + public AutoCompleteBox() : this(DpiManager.Instance) + { + } + + public AutoCompleteBox(IDpiManager dpiManager) + { + Guard.ArgumentNotNull(dpiManager, "dpiManager"); + + CompletionOffset = 0; + IsEnabledChanged += ControlIsEnabledChanged; + Interaction = new InteractionHelper(this); + + // Creating the view here ensures that View is always != null + ClearView(); + + Populating = populatingSubject; + + Populating + .SelectMany(_ => + { + var advisor = Advisor ?? EmptyAutoCompleteAdvisor.Instance; + return advisor.GetAutoCompletionSuggestions(Text, TextBox.CaretIndex); + }) + .ObserveOn(RxApp.MainThreadScheduler) + .Subscribe(result => + { + CompletionOffset = result.Offset; + ItemsSource = result.Suggestions; + PopulateComplete(); + }); + this.dpiManager = dpiManager; + } + + public IObservable Populating { get; private set; } + + public int CompletionOffset + { + get { return (int)GetValue(CompletionOffsetProperty); } + set { SetValue(CompletionOffsetProperty, value); } + } + + // Using a DependencyProperty as the backing store for CompletionOffset. This enables animation, styling, binding, etc... + public static readonly DependencyProperty CompletionOffsetProperty = + DependencyProperty.Register( + "CompletionOffset", + typeof(int), + typeof(AutoCompleteBox), + new PropertyMetadata(0)); + + public Point PopupPosition + { + get + { + var position = TextBox.GetPositionFromCharIndex(CompletionOffset); + var dpi = dpiManager.CurrentDpi; + double verticalOffset = 5.0 - TextBox.Margin.Bottom; + position.Offset(0, verticalOffset); // Vertically pad it. Yeah, Point is mutable. WTF? + return dpi.Scale(position); + } + } + + /// + /// Gets or sets the minimum delay, in milliseconds, after text is typed + /// in the text box before the + /// control + /// populates the list of possible matches in the drop-down. + /// + /// The minimum delay, in milliseconds, after text is typed in + /// the text box, but before the + /// populates + /// the list of possible matches in the drop-down. The default is 0. + /// The set value is less than 0. + public int MinimumPopulateDelay + { + get { return (int)GetValue(MinimumPopulateDelayProperty); } + set { SetValue(MinimumPopulateDelayProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty MinimumPopulateDelayProperty = + DependencyProperty.Register( + "MinimumPopulateDelay", + typeof(int), + typeof(AutoCompleteBox), + new PropertyMetadata(OnMinimumPopulateDelayPropertyChanged)); + + /// + /// MinimumPopulateDelayProperty property changed handler. Any current + /// dispatcher timer will be stopped. The timer will not be restarted + /// until the next TextUpdate call by the user. + /// + /// AutoCompleteTextBox that changed its + /// MinimumPopulateDelay. + /// Event arguments. + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", + Justification = "The exception is most likely to be called through the CLR property setter.")] + private static void OnMinimumPopulateDelayPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var source = d as AutoCompleteBox; + + if (source == null) return; + + if (source.ignorePropertyChange) + { + source.ignorePropertyChange = false; + return; + } + + int newValue = (int)e.NewValue; + if (newValue < 0) + { + source.ignorePropertyChange = true; + d.SetValue(e.Property, e.OldValue); + + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "Invalid value '{0}' for MinimumPopulateDelay", newValue), "e"); + } + + // Resubscribe to TextBox changes with new delay. The easiest way is to just set the TextBox to itself. + var textBox = source.TextBox; + source.TextBox = null; + source.TextBox = textBox; + } + + /// + /// Gets or sets the used + /// to display each item in the drop-down portion of the control. + /// + /// The used to + /// display each item in the drop-down. The default is null. + /// + /// You use the ItemTemplate property to specify the visualization + /// of the data objects in the drop-down portion of the AutoCompleteBox + /// control. If your AutoCompleteBox is bound to a collection and you + /// do not provide specific display instructions by using a + /// DataTemplate, the resulting UI of each item is a string + /// representation of each object in the underlying collection. + /// + public DataTemplate ItemTemplate + { + get { return GetValue(ItemTemplateProperty) as DataTemplate; } + set { SetValue(ItemTemplateProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty ItemTemplateProperty = + DependencyProperty.Register( + "ItemTemplate", + typeof(DataTemplate), + typeof(AutoCompleteBox), + new PropertyMetadata(null)); + + /// + /// Gets or sets the that is + /// applied to the selection adapter contained in the drop-down portion + /// of the + /// control. + /// + /// The applied to the + /// selection adapter contained in the drop-down portion of the + /// control. + /// The default is null. + /// + /// The default selection adapter contained in the drop-down is a + /// ListBox control. + /// + public Style ItemContainerStyle + { + get { return GetValue(ItemContainerStyleProperty) as Style; } + set { SetValue(ItemContainerStyleProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty ItemContainerStyleProperty = + DependencyProperty.Register( + elementItemContainerStyle, + typeof(Style), + typeof(AutoCompleteBox), + new PropertyMetadata(null, null)); + + /// + /// Gets or sets the applied to + /// the text box portion of the + /// control. + /// + /// The applied to the text + /// box portion of the + /// control. + /// The default is null. + public Style TextBoxStyle + { + get { return GetValue(TextBoxStyleProperty) as Style; } + set { SetValue(TextBoxStyleProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty TextBoxStyleProperty = + DependencyProperty.Register( + elementTextBoxStyle, + typeof(Style), + typeof(AutoCompleteBox), + new PropertyMetadata(null)); + + /// + /// Gets or sets the maximum height of the drop-down portion of the + /// control. + /// + /// The maximum height of the drop-down portion of the + /// control. + /// The default is . + /// The specified value is less than 0. + public double MaxDropDownHeight + { + get { return (double)GetValue(MaxDropDownHeightProperty); } + set { SetValue(MaxDropDownHeightProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty MaxDropDownHeightProperty = + DependencyProperty.Register( + "MaxDropDownHeight", + typeof(double), + typeof(AutoCompleteBox), + new PropertyMetadata(double.PositiveInfinity, OnMaxDropDownHeightPropertyChanged)); + + /// + /// MaxDropDownHeightProperty property changed handler. + /// + /// AutoCompleteTextBox that changed its MaxDropDownHeight. + /// Event arguments. + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly" + , Justification = "The exception will be called through a CLR setter in most cases.")] + private static void OnMaxDropDownHeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var source = d as AutoCompleteBox; + if (source == null) return; + if (source.ignorePropertyChange) + { + source.ignorePropertyChange = false; + return; + } + + double newValue = (double)e.NewValue; + + // Revert to the old value if invalid (negative) + if (newValue < 0) + { + source.ignorePropertyChange = true; + source.SetValue(e.Property, e.OldValue); + + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "Invalid value '{0}' for MaxDropDownHeight", e.NewValue), "e"); + } + + source.OnMaxDropDownHeightChanged(newValue); + } + + /// + /// Gets or sets a value indicating whether the drop-down portion of + /// the control is open. + /// + /// + /// True if the drop-down is open; otherwise, false. The default is + /// false. + /// + public bool IsDropDownOpen + { + get { return (bool)GetValue(IsDropDownOpenProperty); } + set + { + HandleShortcutSupport(value); + SetValue(IsDropDownOpenProperty, value); + } + } + + void HandleShortcutSupport(bool value) + { + if (TextBox == null) + { + return; + } + + var shortcutContainer = TextBox.Control as IShortcutContainer; + if (shortcutContainer != null) + { + shortcutContainer.SupportsKeyboardShortcuts = !value && supportsShortcutOriginalValue; + } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty IsDropDownOpenProperty = + DependencyProperty.Register( + "IsDropDownOpen", + typeof(bool), + typeof(AutoCompleteBox), + new PropertyMetadata(false, OnIsDropDownOpenPropertyChanged)); + + /// + /// IsDropDownOpenProperty property changed handler. + /// + /// AutoCompleteTextBox that changed its IsDropDownOpen. + /// Event arguments. + private static void OnIsDropDownOpenPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var source = d as AutoCompleteBox; + + if (source == null) return; + + // Ignore the change if requested + if (source.ignorePropertyChange) + { + source.ignorePropertyChange = false; + return; + } + + bool oldValue = (bool)e.OldValue; + bool newValue = (bool)e.NewValue; + + if (!newValue) + { + source.ClosingDropDown(oldValue); + } + + source.UpdateVisualState(true); + } + + /// + /// Gets or sets a collection that is used to generate the items for the + /// drop-down portion of the + /// control. + /// + /// The collection that is used to generate the items of the + /// drop-down portion of the + /// control. + public IEnumerable ItemsSource + { + get { return GetValue(ItemsSourceProperty) as IEnumerable; } + set { SetValue(ItemsSourceProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register( + "ItemsSource", + typeof(IEnumerable), + typeof(AutoCompleteBox), + new PropertyMetadata(OnItemsSourcePropertyChanged)); + + /// + /// ItemsSourceProperty property changed handler. + /// + /// AutoCompleteBox that changed its ItemsSource. + /// Event arguments. + private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var autoComplete = d as AutoCompleteBox; + if (autoComplete == null) return; + autoComplete.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue); + } + + /// + /// Gets or sets the selected item in the drop-down. + /// + /// The selected item in the drop-down. + /// + /// If the IsTextCompletionEnabled property is true and text typed by + /// the user matches an item in the ItemsSource collection, which is + /// then displayed in the text box, the SelectedItem property will be + /// a null reference. + /// + public object SelectedItem + { + get { return GetValue(SelectedItemProperty); } + set { SetValue(SelectedItemProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier the + /// + /// dependency property. + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register( + "SelectedItem", + typeof(object), + typeof(AutoCompleteBox), + new PropertyMetadata()); + + private void CancelSuggestion() + { + Debug.Assert(TextBox != null, "TextBox is somehow null"); + Debug.Assert(Text != null, "Text is somehow null"); + + DismissDropDown(); + + Debug.Assert(0 == TextBox.SelectionLength, "SelectionLength is what I think it is"); + } + + private void ExpandSuggestion(string value) + { + Debug.Assert(value != null, "The string passed into ExpandSuggestion should not be null"); + Debug.Assert(TextBox != null, "TextBox is somehow null"); + Debug.Assert(Text != null, "Text is somehow null"); + + var newText = TextBox.GetExpandedText(value, CompletionOffset); + UpdateTextValue(newText); + + // New caret index should be one space after the inserted text. + int newCaretIndex = CompletionOffset + value.Length + 1; + TextBox.CaretIndex = newCaretIndex; + Debug.Assert(newCaretIndex == TextBox.SelectionStart, + String.Format(CultureInfo.InvariantCulture, + "SelectionStart '{0}' should be the same as newCaretIndex '{1}'", + TextBox.SelectionStart, newCaretIndex)); + Debug.Assert(0 == TextBox.SelectionLength, + String.Format(CultureInfo.InvariantCulture, + "SelectionLength should be 0 but is '{0}' is what I think it is", + TextBox.SelectionStart)); + } + + /// + /// Gets or sets the text in the text box portion of the + /// control. + /// + /// The text in the text box portion of the + /// control. + public string Text + { + get { return GetValue(TextProperty) as string; } + set { SetValue(TextProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register( + "Text", + typeof(string), + typeof(AutoCompleteBox), + new PropertyMetadata(string.Empty, OnTextPropertyChanged)); + + /// + /// TextProperty property changed handler. + /// + /// AutoCompleteBox that changed its Text. + /// Event arguments. + private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var source = d as AutoCompleteBox; + if (source == null) return; + + source.OnTextPropertyChanged((string) e.NewValue); + } + + /// + /// Gets or sets the drop down popup control. + /// + private PopupHelper DropDownPopup { get; set; } + + /// + /// The TextBox template part. + /// + private IAutoCompleteTextInput textInput; + + /// + /// The SelectionAdapter. + /// + private ISelectionAdapter adapter; + + /// + /// Gets or sets the Text template part. + /// + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] + public IAutoCompleteTextInput TextBox + { + get { return textInput; } + set { UpdateTextBox(value); } + } + + void UpdateTextBox(IAutoCompleteTextInput value) + { + // Detach existing handlers + if (textInput != null) + { + UnsubscribeToEvent("SelectionChanged"); + UnsubscribeToEvent("OnTextBoxTextChanged"); + } + + textInput = value; + + // Attach handlers + if (textInput != null) + { + var shortcutContainer = textInput.Control as IShortcutContainer; + if (shortcutContainer != null) + { + supportsShortcutOriginalValue = shortcutContainer.SupportsKeyboardShortcuts; + } + + SubscribeToEvent("OnTextBoxTextChanged", + ObserveTextBoxChanges().Subscribe(shouldPopulate => + { + if (shouldPopulate) + { + PopulateDropDown(); + } + else + { + DismissDropDown(); + } + })); + + if (Text != null) + { + UpdateTextValue(Text); + } + } + } + + IObservable ObserveTextBoxChanges() + { + var distinctTextChanges = textInput + .TextChanged + .Select(_ => textInput.Text ?? "") + .DistinctUntilChanged(); + + if (MinimumPopulateDelay >= 0) + { + distinctTextChanges = distinctTextChanges + .Throttle(TimeSpan.FromMilliseconds(MinimumPopulateDelay), RxApp.MainThreadScheduler); + } + + return distinctTextChanges + .Select(text => { + bool userChangedTextBox = ignoreTextPropertyChange == 0; + if (ignoreTextPropertyChange > 0) ignoreTextPropertyChange--; + + return new { Text = text, ShouldPopulate = text.Length > 0 && userChangedTextBox }; + }) + .Do(textInfo => + { + userCalledPopulate = textInfo.ShouldPopulate; + UpdateAutoCompleteTextValue(textInfo.Text); + }) + .Select(textInfo => textInfo.ShouldPopulate); + } + + /// + /// Gets or sets the selection adapter used to populate the drop-down + /// with a list of selectable items. + /// + /// The selection adapter used to populate the drop-down with a + /// list of selectable items. + /// + /// You can use this property when you create an automation peer to sw + /// use with AutoCompleteBox or deriving from AutoCompleteBox to + /// create a custom control. + /// + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] + public ISelectionAdapter SelectionAdapter + { + get { return adapter; } + set + { + if (adapter != null) + { + adapter.SelectionChanged -= OnAdapterSelectionChanged; + adapter.Commit -= OnAdapterSelectionComplete; + adapter.Cancel -= OnAdapterSelectionCanceled; + adapter.ItemsSource = null; + } + + adapter = value; + + if (adapter != null) + { + adapter.SelectionChanged += OnAdapterSelectionChanged; + adapter.Commit += OnAdapterSelectionComplete; + adapter.Cancel += OnAdapterSelectionCanceled; + adapter.ItemsSource = view; + } + } + } + + /// + /// Provides suggestions based on what's been typed. + /// + public IAutoCompleteAdvisor Advisor + { + get; + set; + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty AdvisorProperty = + DependencyProperty.Register( + "Advisor", + typeof(IAutoCompleteAdvisor), + typeof(AutoCompleteBox), + new PropertyMetadata(null, OnAdvisorPropertyChanged)); + + /// + /// AdvisorProperty property changed handler. + /// + /// AutoCompleteBox that changed its Advisor. + /// Event arguments. + private static void OnAdvisorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + var source = d as AutoCompleteBox; + if (source == null) return; + + source.Advisor = (IAutoCompleteAdvisor)e.NewValue; + } + + /// + /// Builds the visual tree for the + /// control + /// when a new template is applied. + /// + public override void OnApplyTemplate() + { + if (TextBox != null) + { + UnsubscribeToEvent("PreviewKeyDown"); + } + + if (DropDownPopup != null) + { + DropDownPopup.Closed -= OnDropDownClosed; + DropDownPopup.FocusChanged -= OnDropDownFocusChanged; + DropDownPopup.UpdateVisualStates -= OnDropDownPopupUpdateVisualStates; + DropDownPopup.BeforeOnApplyTemplate(); + DropDownPopup = null; + } + + base.OnApplyTemplate(); + + // Set the template parts. Individual part setters remove and add + // any event handlers. + var popup = GetTemplateChild(elementPopup) as Popup; + if (popup != null) + { + DropDownPopup = new PopupHelper(this, popup) + { + MaxDropDownHeight = MaxDropDownHeight + }; + DropDownPopup.AfterOnApplyTemplate(); + DropDownPopup.Closed += OnDropDownClosed; + DropDownPopup.FocusChanged += OnDropDownFocusChanged; + DropDownPopup.UpdateVisualStates += OnDropDownPopupUpdateVisualStates; + } + SelectionAdapter = GetSelectionAdapterPart(); + // TODO: eliminate duplication between these two elements... + TextBox = InputElement; + + if (TextBox != null) + { + SubscribeToEvent("PreviewKeyDown", TextBox.PreviewKeyDown.Subscribe(OnTextBoxPreviewKeyDown)); + } + + Interaction.OnApplyTemplateBase(); + + // If the drop down property indicates that the popup is open, + // flip its value to invoke the changed handler. + if (IsDropDownOpen && DropDownPopup != null && !DropDownPopup.IsOpen) + { + OpeningDropDown(); + } + } + + /// + /// Allows the popup wrapper to fire visual state change events. + /// + /// The source object. + /// The event data. + private void OnDropDownPopupUpdateVisualStates(object sender, EventArgs e) + { + UpdateVisualState(true); + } + + /// + /// Allows the popup wrapper to fire the FocusChanged event. + /// + /// The source object. + /// The event data. + private void OnDropDownFocusChanged(object sender, EventArgs e) + { + FocusChanged(HasFocus()); + } + + /// + /// Begin closing the drop-down. + /// + /// The original value. + private void ClosingDropDown(bool oldValue) + { + bool delayedClosingVisual = false; + if (DropDownPopup != null) + { + delayedClosingVisual = DropDownPopup.UsesClosingVisualState; + } + + if (view == null || view.Count == 0) + { + delayedClosingVisual = false; + } + + // Immediately close the drop down window: + // When a popup closed visual state is present, the code path is + // slightly different and the actual call to CloseDropDown will + // be called only after the visual state's transition is done + RaiseExpandCollapseAutomationEvent(oldValue, false); + if (!delayedClosingVisual) + { + CloseDropDown(); + } + + UpdateVisualState(true); + } + + private void OpeningDropDown() + { + OpenDropDown(); + + UpdateVisualState(true); + } + + /// + /// Raise an expand/collapse event through the automation peer. + /// + /// The old value. + /// The new value. + private void RaiseExpandCollapseAutomationEvent(bool oldValue, bool newValue) + { + var peer = UIElementAutomationPeer.FromElement(this) as AutoCompleteBoxAutomationPeer; + if (peer != null) + { + peer.RaiseExpandCollapseAutomationEvent(oldValue, newValue); + } + } + + /// + /// Handles the PreviewKeyDown event on the TextBox for WPF. + /// + /// The event data. + private void OnTextBoxPreviewKeyDown(EventPattern e) + { + OnKeyDown(e.EventArgs); + } + + /// + /// Connects to the DropDownPopup Closed event. + /// + /// The source object. + /// The event data. + private void OnDropDownClosed(object sender, EventArgs e) + { + // Force the drop down dependency property to be false. + if (IsDropDownOpen) + { + IsDropDownOpen = false; + } + } + + /// + /// Creates an + /// + /// + /// A + /// + /// for the + /// object. + protected override AutomationPeer OnCreateAutomationPeer() + { + return new AutoCompleteBoxAutomationPeer(this); + } + + /// + /// Handles the FocusChanged event. + /// + /// A value indicating whether the control + /// currently has the focus. + private void FocusChanged(bool hasFocus) + { + // The OnGotFocus & OnLostFocus are asynchronously and cannot + // reliably tell you that have the focus. All they do is let you + // know that the focus changed sometime in the past. To determine + // if you currently have the focus you need to do consult the + // FocusManager (see HasFocus()). + + if (!hasFocus) + { + IsDropDownOpen = false; + userCalledPopulate = false; + } + } + + /// + /// Determines whether the text box or drop-down portion of the + /// control has + /// focus. + /// + /// true to indicate the + /// has focus; + /// otherwise, false. + protected bool HasFocus() + { + var focused = + // For WPF, check if the element that has focus is within the control, as + // FocusManager.GetFocusedElement(this) will return null in such a case. + IsKeyboardFocusWithin ? Keyboard.FocusedElement as DependencyObject : FocusManager.GetFocusedElement(this) as DependencyObject; + + while (focused != null) + { + if (ReferenceEquals(focused, this)) + { + return true; + } + + // This helps deal with popups that may not be in the same + // visual tree + var parent = VisualTreeHelper.GetParent(focused); + if (parent == null) + { + // Try the logical parent. + var element = focused as FrameworkElement; + if (element != null) + { + parent = element.Parent; + } + } + focused = parent; + } + return false; + } + + /// + /// Provides handling for the + /// event. + /// + /// A + /// that contains the event data. + protected override void OnGotFocus(RoutedEventArgs e) + { + base.OnGotFocus(e); + FocusChanged(HasFocus()); + } + + /// + /// Handles change of keyboard focus, which is treated differently than control focus + /// + /// + protected override void OnIsKeyboardFocusWithinChanged(DependencyPropertyChangedEventArgs e) + { + base.OnIsKeyboardFocusWithinChanged(e); + FocusChanged((bool)e.NewValue); + } + + /// + /// Provides handling for the + /// event. + /// + /// A + /// that contains the event data. + protected override void OnLostFocus(RoutedEventArgs e) + { + base.OnLostFocus(e); + FocusChanged(HasFocus()); + } + + /// + /// Handle the change of the IsEnabled property. + /// + /// The source object. + /// The event data. + private void ControlIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + bool isEnabled = (bool)e.NewValue; + if (!isEnabled) + { + IsDropDownOpen = false; + } + } + + /// + /// Returns the + /// part, if + /// possible. + /// + /// + /// A object, + /// if possible. Otherwise, null. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", + Justification = "Following the GetTemplateChild pattern for the method.")] + protected virtual ISelectionAdapter GetSelectionAdapterPart() + { + var selector = GetTemplateChild(elementSelector) as Selector; + if (selector != null) + { + // Built in support for wrapping a Selector control + adapter = new SelectorSelectionAdapter(selector); + } + return adapter; + } + + /// + /// Populates the drop down + /// + private void PopulateDropDown() + { + populatingSubject.OnNext(Unit.Default); + } + + void DismissDropDown() + { + SelectedItem = null; + if (IsDropDownOpen) + { + IsDropDownOpen = false; + } + } + + /// + /// Converts the specified object to a string by using the + /// and + /// values + /// of the binding object specified by the + /// + /// property. + /// + /// The object to format as a string. + /// The string representation of the specified object. + /// + /// Override this method to provide a custom string conversion. + /// + protected virtual string FormatValue(object value) + { + return value == null ? string.Empty : value.ToString(); + } + + /// + /// Updates both the text box value and underlying text dependency + /// property value if and when they change. Automatically fires the + /// text changed events when there is a change. + /// + /// The new string value. + private void UpdateTextValue(string value) + { + UpdateAutoCompleteTextValue(value); + UpdateTextBoxValue(value); + } + + // Update the TextBox's Text dependency property + void UpdateTextBoxValue(string value) + { + var newValue = value ?? string.Empty; + + if (TextBox == null || TextBox.Text == newValue) + { + return; + } + + ignoreTextPropertyChange++; + TextBox.Text = newValue; + } + + void UpdateAutoCompleteTextValue(string value) + { + if (Text == value) return; + + ignoreTextPropertyChange++; + Text = value; + } + + /// + /// Handle the update of the text when the Text dependency property changes. + /// + /// The new text. + private void OnTextPropertyChanged(string newText) + { + // Only process this event if it is coming from someone outside + // setting the Text dependency property directly. + if (ignoreTextPropertyChange > 0) + { + ignoreTextPropertyChange--; + return; + } + + UpdateTextBoxValue(newText); + } + + /// + /// Notifies the + /// that the + /// + /// property has been set and the data can be filtered to provide + /// possible matches in the drop-down. + /// + /// + /// Call this method when you are providing custom population of + /// the drop-down portion of the AutoCompleteBox, to signal the control + /// that you are done with the population process. + /// Typically, you use PopulateComplete when the population process + /// is a long-running process and you want to cancel built-in filtering + /// of the ItemsSource items. In this case, you can handle the + /// Populated event and set PopulatingEventArgs.Cancel to true. + /// When the long-running process has completed you call + /// PopulateComplete to indicate the drop-down is populated. + /// + protected void PopulateComplete() + { + RefreshView(); + + if (SelectionAdapter != null && !Equals(SelectionAdapter.ItemsSource, view)) + { + SelectionAdapter.ItemsSource = view; + } + + bool isDropDownOpen = userCalledPopulate && (view.Count > 0); + if (isDropDownOpen != IsDropDownOpen) + { + ignorePropertyChange = true; + IsDropDownOpen = isDropDownOpen; + } + if (IsDropDownOpen) + { + OpeningDropDown(); + } + else + { + ClosingDropDown(true); + } + + // We always want to select the first suggestion after populating the drop down. + SelectFirstItem(); + } + + void SelectFirstItem() + { + if (!view.Any()) return; + + var newSelectedItem = view.First(); + SelectionAdapter.SelectedItem = newSelectedItem; + SelectedItem = newSelectedItem; + } + + + /// + /// A simple helper method to clear the view and ensure that a view + /// object is always present and not null. + /// + private void ClearView() + { + if (view == null) + { + view = new ObservableCollection(); + } + else + { + view.Clear(); + } + } + + /// + /// Walks through the items enumeration. Performance is not going to be perfect with the current implementation. + /// + private void RefreshView() + { + if (suggestions == null) + { + ClearView(); + return; + } + + int viewIndex = 0; + int viewCount = view.Count; + var items = suggestions; + foreach (var item in items) + { + if (viewCount > viewIndex && view[viewIndex] == item) + { + // Item is still in the view + viewIndex++; + } + else + { + // Insert the item + if (viewCount > viewIndex && view[viewIndex] != item) + { + // Replace item + // Unfortunately replacing via index throws a fatal + // exception: View[view_index] = item; + // Cost: O(n) vs O(1) + view.RemoveAt(viewIndex); + view.Insert(viewIndex, item); + viewIndex++; + } + else + { + // Add the item + if (viewIndex == viewCount) + { + // Constant time is preferred (Add). + view.Add(item); + } + else + { + view.Insert(viewIndex, item); + } + viewIndex++; + viewCount++; + } + } + } + } + + /// + /// Handle any change to the ItemsSource dependency property, update + /// the underlying ObservableCollection view, and set the selection + /// adapter's ItemsSource to the view if appropriate. + /// + /// The old enumerable reference. + /// The new enumerable reference. + private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) + { + // Remove handler for oldValue.CollectionChanged (if present) + var oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged; + if (null != oldValueINotifyCollectionChanged && null != collectionChangedWeakEventListener) + { + collectionChangedWeakEventListener.Detach(); + collectionChangedWeakEventListener = null; + } + + // Add handler for newValue.CollectionChanged (if possible) + var newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged; + if (null != newValueINotifyCollectionChanged) + { + collectionChangedWeakEventListener = new WeakEventListener(this) + { + OnEventAction = + (instance, source, eventArgs) => instance.ItemsSourceCollectionChanged(eventArgs), + OnDetachAction = + weakEventListener => + newValueINotifyCollectionChanged.CollectionChanged -= weakEventListener.OnEvent + }; + newValueINotifyCollectionChanged.CollectionChanged += collectionChangedWeakEventListener.OnEvent; + } + + // Store a local cached copy of the data + suggestions = newValue == null ? null : new List(newValue.Cast().ToList()); + + // Clear and set the view on the selection adapter + ClearView(); + if (SelectionAdapter != null && !Equals(SelectionAdapter.ItemsSource, view)) + { + SelectionAdapter.ItemsSource = view; + } + if (IsDropDownOpen) + { + RefreshView(); + } + } + + /// + /// Method that handles the ObservableCollection.CollectionChanged event for the ItemsSource property. + /// + /// The event data. + private void ItemsSourceCollectionChanged(NotifyCollectionChangedEventArgs e) + { + // Update the cache + if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) + { + for (int index = 0; index < e.OldItems.Count; index++) + { + suggestions.RemoveAt(e.OldStartingIndex); + } + } + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && suggestions.Count >= e.NewStartingIndex) + { + for (int index = 0; index < e.NewItems.Count; index++) + { + suggestions.Insert(e.NewStartingIndex + index, e.NewItems[index]); + } + } + if (e.Action == NotifyCollectionChangedAction.Replace && e.NewItems != null && e.OldItems != null) + { + foreach (var t in e.NewItems) + { + suggestions[e.NewStartingIndex] = t; + } + } + + // Update the view + if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) + { + if (e.OldItems != null) + { + foreach (var t in e.OldItems) + { + view.Remove(t); + } + } + } + + if (e.Action == NotifyCollectionChangedAction.Reset) + { + // Significant changes to the underlying data. + ClearView(); + if (ItemsSource != null) + { + suggestions = new List(ItemsSource.Cast().ToList()); + } + } + + // Refresh the observable collection used in the selection adapter. + RefreshView(); + } + + /// + /// Handles the SelectionChanged event of the selection adapter. + /// + /// The source object. + /// The selection changed event data. + private void OnAdapterSelectionChanged(object sender, SelectionChangedEventArgs e) + { + SelectedItem = adapter.SelectedItem; + } + + /// + /// Handles the Commit event on the selection adapter. + /// + /// The source object. + /// The event data. + private void OnAdapterSelectionComplete(object sender, RoutedEventArgs e) + { + IsDropDownOpen = false; + + var selectedItem = SelectedItem; + + // Completion will update the selected value + ExpandSuggestion(selectedItem == null ? string.Empty : selectedItem.ToString()); + + // This forces the textbox to get keyboard focus, in the case where + // another part of the control may have temporarily received focus. + if (TextBox != null) + { + // Because LOL WPF focus shit, we need to make sure don't lose the caret index when we give this focus. + int caretIndex = TextBox.CaretIndex; + TextBox.Focus(); + TextBox.CaretIndex = caretIndex; + } + else + { + Focus(); + } + } + + /// + /// Handles the Cancel event on the selection adapter. + /// + /// The source object. + /// The event data. + private void OnAdapterSelectionCanceled(object sender, RoutedEventArgs e) + { + IsDropDownOpen = false; + + CancelSuggestion(); + + // This forces the textbox to get keyboard focus, in the case where + // another part of the control may have temporarily received focus. + if (TextBox != null) + { + TextBox.Focus(); + } + else + { + Focus(); + } + } + + /// + /// Handles MaxDropDownHeightChanged by re-arranging and updating the + /// popup arrangement. + /// + /// The new value. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "newValue", + Justification = "This makes it easy to add validation or other changes in the future.")] + private void OnMaxDropDownHeightChanged(double newValue) + { + if (DropDownPopup != null) + { + DropDownPopup.MaxDropDownHeight = newValue; + } + UpdateVisualState(true); + } + + private void OpenDropDown() + { + if (DropDownPopup != null) + { + DropDownPopup.IsOpen = true; + } + popupHasOpened = true; + } + + private void CloseDropDown() + { + if (popupHasOpened) + { + if (SelectionAdapter != null) + { + SelectionAdapter.SelectedItem = null; + } + if (DropDownPopup != null) + { + DropDownPopup.IsOpen = false; + } + } + } + + /// + /// Provides handling for the + /// event. + /// + /// A + /// that contains the event data. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0")] + protected override void OnKeyDown(KeyEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + base.OnKeyDown(e); + + if (e.Handled || !IsEnabled) + { + return; + } + + // The drop down is open, pass along the key event arguments to the + // selection adapter. If it isn't handled by the adapter's logic, + // then we handle some simple navigation scenarios for controlling + // the drop down. + if (IsDropDownOpen) + { + if (SelectionAdapter != null) + { + SelectionAdapter.HandleKeyDown(e); + if (e.Handled) + { + return; + } + } + + if (e.Key == Key.Escape) + { + OnAdapterSelectionCanceled(this, new RoutedEventArgs()); + e.Handled = true; + } + } + + // Standard drop down navigation + switch (e.Key) + { +// case Key.F4: +// IsDropDownOpen = !IsDropDownOpen; +// e.Handled = true; +// break; + + case Key.Enter: + if (IsDropDownOpen && SelectedItem != null) + { + OnAdapterSelectionComplete(this, new RoutedEventArgs()); + e.Handled = true; + } + + break; + } + } + + /// + /// Update the visual state of the control. + /// + /// + /// A value indicating whether to automatically generate transitions to + /// the new state, or instantly transition to the new state. + /// + void IUpdateVisualState.UpdateVisualState(bool useTransitions) + { + UpdateVisualState(useTransitions); + } + + /// + /// Update the current visual state of the button. + /// + /// + /// True to use transitions when updating the visual state, false to + /// snap directly to the new visual state. + /// + internal virtual void UpdateVisualState(bool useTransitions) + { + // Popup + VisualStateManager.GoToState(this, IsDropDownOpen ? VisualStates.StatePopupOpened : VisualStates.StatePopupClosed, useTransitions); + + // Handle the Common and Focused states + Interaction.UpdateVisualStateBase(useTransitions); + } + + private class EmptyAutoCompleteAdvisor : IAutoCompleteAdvisor + { + public static readonly IAutoCompleteAdvisor Instance = new EmptyAutoCompleteAdvisor(); + + private EmptyAutoCompleteAdvisor() + { + } + + public IObservable GetAutoCompletionSuggestions(string text, int caretPosition) + { + return Observable.Empty(); + } + } + + private void SubscribeToEvent(string eventName, IDisposable disposable) + { + eventSubscriptions[eventName] = disposable; + } + + private void UnsubscribeToEvent(string eventName) + { + IDisposable disposable; + if (eventSubscriptions.TryGetValue(eventName, out disposable)) + { + disposable.Dispose(); + } + } + + public IAutoCompleteTextInput InputElement + { + get { return (IAutoCompleteTextInput)GetValue(InputElementProperty); } + set { SetValue(InputElementProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty InputElementProperty = + DependencyProperty.Register( + "InputElement", + typeof(IAutoCompleteTextInput), + typeof(AutoCompleteBox)); + } +} diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/AutoCompleteBoxAutomationPeer.cs b/src/GitHub.UI/Controls/AutoCompleteBox/AutoCompleteBoxAutomationPeer.cs new file mode 100644 index 0000000000..5ac3965c47 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/AutoCompleteBoxAutomationPeer.cs @@ -0,0 +1,300 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. +// All other rights reserved. + +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Automation; +using System.Windows.Automation.Peers; +using System.Windows.Automation.Provider; + +namespace GitHub.UI +{ + /// + /// Exposes AutoCompleteBox types to UI Automation. + /// + /// Stable + public sealed class AutoCompleteBoxAutomationPeer : FrameworkElementAutomationPeer, IValueProvider, IExpandCollapseProvider, ISelectionProvider + { + /// + /// The name reported as the core class name. + /// + private const string autoCompleteBoxClassNameCore = "AutoCompleteBox"; + + /// + /// Gets the AutoCompleteBox that owns this + /// AutoCompleteBoxAutomationPeer. + /// + private AutoCompleteBox OwnerAutoCompleteBox + { + get { return (AutoCompleteBox)Owner; } + } + + /// + /// Gets a value indicating whether the UI automation provider allows + /// more than one child element to be selected concurrently. + /// + /// + /// This API supports the .NET Framework infrastructure and is not + /// intended to be used directly from your code. + /// + /// True if multiple selection is allowed; otherwise, false. + bool ISelectionProvider.CanSelectMultiple + { + get { return false; } + } + + /// + /// Gets a value indicating whether the UI automation provider + /// requires at least one child element to be selected. + /// + /// + /// This API supports the .NET Framework infrastructure and is not + /// intended to be used directly from your code. + /// + /// True if selection is required; otherwise, false. + bool ISelectionProvider.IsSelectionRequired + { + get { return false; } + } + + /// + /// Initializes a new instance of the AutoCompleteBoxAutomationPeer + /// class. + /// + /// + /// The AutoCompleteBox that is associated with this + /// AutoCompleteBoxAutomationPeer. + /// + public AutoCompleteBoxAutomationPeer(AutoCompleteBox owner) + : base(owner) + { + } + + /// + /// Gets the control type for the AutoCompleteBox that is associated + /// with this AutoCompleteBoxAutomationPeer. This method is called by + /// GetAutomationControlType. + /// + /// ComboBox AutomationControlType. + protected override AutomationControlType GetAutomationControlTypeCore() + { + return AutomationControlType.ComboBox; + } + + /// + /// Gets the name of the AutoCompleteBox that is associated with this + /// AutoCompleteBoxAutomationPeer. This method is called by + /// GetClassName. + /// + /// The name AutoCompleteBox. + protected override string GetClassNameCore() + { + return autoCompleteBoxClassNameCore; + } + + /// + /// Gets the control pattern for the AutoCompleteBox that is associated + /// with this AutoCompleteBoxAutomationPeer. + /// + /// The desired PatternInterface. + /// The desired AutomationPeer or null. + public override object GetPattern(PatternInterface patternInterface) + { + object iface = null; + var owner = OwnerAutoCompleteBox; + + if (patternInterface == PatternInterface.Value) + { + iface = this; + } + else if (patternInterface == PatternInterface.ExpandCollapse) + { + iface = this; + } + else if (owner.SelectionAdapter != null) + { + var peer = owner.SelectionAdapter.CreateAutomationPeer(); + if (peer != null) + { + iface = peer.GetPattern(patternInterface); + } + } + + return iface ?? base.GetPattern(patternInterface); + } + + /// + /// Blocking method that returns after the element has been expanded. + /// + /// + /// This API supports the .NET Framework infrastructure and is not + /// intended to be used directly from your code. + /// + void IExpandCollapseProvider.Expand() + { + if (!IsEnabled()) + { + throw new ElementNotEnabledException(); + } + + OwnerAutoCompleteBox.IsDropDownOpen = true; + } + + /// + /// Blocking method that returns after the element has been collapsed. + /// + /// + /// This API supports the .NET Framework infrastructure and is not + /// intended to be used directly from your code. + /// + void IExpandCollapseProvider.Collapse() + { + if (!IsEnabled()) + { + throw new ElementNotEnabledException(); + } + + OwnerAutoCompleteBox.IsDropDownOpen = false; + } + + /// + /// Gets an element's current Collapsed or Expanded state. + /// + /// + /// This API supports the .NET Framework infrastructure and is not + /// intended to be used directly from your code. + /// + ExpandCollapseState IExpandCollapseProvider.ExpandCollapseState + { + get + { + return OwnerAutoCompleteBox.IsDropDownOpen ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed; + } + } + + /// + /// Raises the ExpandCollapse automation event. + /// + /// The old value. + /// The new value. + internal void RaiseExpandCollapseAutomationEvent(bool oldValue, bool newValue) + { + RaisePropertyChangedEvent( + ExpandCollapsePatternIdentifiers.ExpandCollapseStateProperty, + oldValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed, + newValue ? ExpandCollapseState.Expanded : ExpandCollapseState.Collapsed); + } + + /// + /// Sets the value of a control. + /// + /// The value to set. The provider is responsible + /// for converting the value to the appropriate data type. + void IValueProvider.SetValue(string value) + { + OwnerAutoCompleteBox.Text = value; + } + + /// + /// Gets a value indicating whether the value of a control is + /// read-only. + /// + /// True if the value is read-only; false if it can be modified. + bool IValueProvider.IsReadOnly + { + get + { + return !OwnerAutoCompleteBox.IsEnabled; + } + } + + /// + /// Gets the value of the control. + /// + /// The value of the control. + string IValueProvider.Value + { + get + { + return OwnerAutoCompleteBox.Text ?? string.Empty; + } + } + + /// + /// Gets the collection of child elements of the AutoCompleteBox that + /// are associated with this AutoCompleteBoxAutomationPeer. This method + /// is called by GetChildren. + /// + /// + /// A collection of automation peer elements, or an empty collection + /// if there are no child elements. + /// + [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Required by automation")] + protected override List GetChildrenCore() + { + var children = new List(); + var owner = OwnerAutoCompleteBox; + + // TextBox part. + var textBox = owner.TextBox; + if (textBox != null) + { + var peer = CreatePeerForElement(textBox.Control); + if (peer != null) + { + children.Insert(0, peer); + } + } + + // Include SelectionAdapter's children. + if (owner.SelectionAdapter != null) + { + var selectionAdapterPeer = owner.SelectionAdapter.CreateAutomationPeer(); + if (selectionAdapterPeer != null) + { + var listChildren = selectionAdapterPeer.GetChildren(); + if (listChildren != null) + { + children.AddRange(listChildren); + } + } + } + + return children; + } + + /// + /// Retrieves a UI automation provider for each child element that is + /// selected. + /// + /// An array of UI automation providers. + /// + /// This API supports the .NET Framework infrastructure and is not + /// intended to be used directly from your code. + /// + IRawElementProviderSimple[] ISelectionProvider.GetSelection() + { + if (OwnerAutoCompleteBox.SelectionAdapter != null) + { + var selectedItem = OwnerAutoCompleteBox.SelectionAdapter.SelectedItem; + if (selectedItem != null) + { + var uie = selectedItem as UIElement; + if (uie != null) + { + var peer = CreatePeerForElement(uie); + if (peer != null) + { + return new[] { ProviderFromPeer(peer) }; + } + } + } + } + + return new IRawElementProviderSimple[] { }; + } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/AutoCompleteTextInputExtensions.cs b/src/GitHub.UI/Controls/AutoCompleteBox/AutoCompleteTextInputExtensions.cs new file mode 100644 index 0000000000..abad932547 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/AutoCompleteTextInputExtensions.cs @@ -0,0 +1,37 @@ +using System; +using System.Diagnostics; +using GitHub.Extensions; +using GitHub.Helpers; + +namespace GitHub.UI.Controls.AutoCompleteBox +{ + public static class AutoCompleteTextInputExtensions + { + /// + /// Given a text input and the current value, returns the expected new text. + /// + /// + /// + /// + /// + public static string GetExpandedText(this IAutoCompleteTextInput textInput, string value, int completionOffset) + { + Guard.ArgumentNotNull(textInput, "textInput"); + Guard.ArgumentNotNull(value, "value"); + + int caretIndex = textInput.CaretIndex; + int afterIndex = Math.Max(caretIndex, textInput.SelectionLength + textInput.SelectionStart); + int offset = completionOffset; + + var currentText = textInput.Text ?? ""; // Playing it safe + + if (offset > currentText.Length) throw new InvalidOperationException("The offset can't be larger than the current text length"); + if (afterIndex > currentText.Length) throw new InvalidOperationException("The afterIndex can't be larger than the current text length"); + + var before = currentText.Substring(0, offset); + var after = currentText.Substring(afterIndex); + string prefix = before + value + " "; + return prefix + after; + } + } +} diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/IAutoCompleteTextInput.cs b/src/GitHub.UI/Controls/AutoCompleteBox/IAutoCompleteTextInput.cs new file mode 100644 index 0000000000..95446edf45 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/IAutoCompleteTextInput.cs @@ -0,0 +1,29 @@ +using System; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Reactive; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace GitHub.UI +{ + public interface IAutoCompleteTextInput : INotifyPropertyChanged + { + void Focus(); + [SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Select", + Justification = "Matches the underlying control method name")] + void Select(int position, int length); + void SelectAll(); + int CaretIndex { get; set; } + int SelectionStart { get; } + int SelectionLength { get; } + string Text { get; set; } + IObservable> PreviewKeyDown { get; } + IObservable> SelectionChanged { get; } + IObservable> TextChanged { get; } + UIElement Control { get; } + Point GetPositionFromCharIndex(int charIndex); + Thickness Margin { get; set; } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/IPopupTarget.cs b/src/GitHub.UI/Controls/AutoCompleteBox/IPopupTarget.cs new file mode 100644 index 0000000000..250fa9bca9 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/IPopupTarget.cs @@ -0,0 +1,18 @@ + +using System.Windows; + +namespace GitHub.UI.Controls +{ + /// + /// Controls that implement this interface can specify where an associated popup should be located. + /// + /// + /// The PopupHelper is a generic class for managing Popups that align to the bottom of their associated control. + /// However, our AutoCompleteBox needs the Popup to align to where the completion is happening. Intellisense™ + /// controls behave in a similar fashion. We might find popups useful elsewhere. + /// + public interface IPopupTarget + { + Point PopupPosition { get; } + } +} diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/ISelectionAdapter.cs b/src/GitHub.UI/Controls/AutoCompleteBox/ISelectionAdapter.cs new file mode 100644 index 0000000000..191d09675d --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/ISelectionAdapter.cs @@ -0,0 +1,73 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. +// All other rights reserved. + +using System.Collections; +using System.Windows; +using System.Windows.Automation.Peers; +using System.Windows.Controls; +using System.Windows.Input; + +namespace GitHub.UI +{ + /// + /// Defines an item collection, selection members, and key handling for the + /// selection adapter contained in the drop-down portion of an + /// control. + /// + /// Stable + public interface ISelectionAdapter + { + /// + /// Gets or sets the selected item. + /// + /// The currently selected item. + object SelectedItem { get; set; } + + /// + /// Occurs when the + /// + /// property value changes. + /// + event SelectionChangedEventHandler SelectionChanged; + + /// + /// Gets or sets a collection that is used to generate content for the + /// selection adapter. + /// + /// The collection that is used to generate content for the + /// selection adapter. + IEnumerable ItemsSource { get; set; } + + /// + /// Occurs when a selected item is not cancelled and is committed as the + /// selected item. + /// + event RoutedEventHandler Commit; + + /// + /// Occurs when a selection has been canceled. + /// + event RoutedEventHandler Cancel; + + /// + /// Provides handling for the + /// event that occurs + /// when a key is pressed while the drop-down portion of the + /// has focus. + /// + /// A + /// that contains data about the + /// event. + void HandleKeyDown(KeyEventArgs e); + + /// + /// Returns an automation peer for the selection adapter, for use by the + /// Silverlight automation infrastructure. + /// + /// An automation peer for the selection adapter, if one is + /// available; otherwise, null. + AutomationPeer CreateAutomationPeer(); + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/IUpdateVisualState.cs b/src/GitHub.UI/Controls/AutoCompleteBox/IUpdateVisualState.cs new file mode 100644 index 0000000000..abbecd5f85 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/IUpdateVisualState.cs @@ -0,0 +1,23 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. +// All other rights reserved. + +namespace GitHub.UI +{ + /// + /// The IUpdateVisualState interface is used to provide the + /// InteractionHelper with access to the type's UpdateVisualState method. + /// + internal interface IUpdateVisualState + { + /// + /// Update the visual state of the control. + /// + /// + /// A value indicating whether to automatically generate transitions to + /// the new state, or instantly transition to the new state. + /// + void UpdateVisualState(bool useTransitions); + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/InteractionHelper.cs b/src/GitHub.UI/Controls/AutoCompleteBox/InteractionHelper.cs new file mode 100644 index 0000000000..33dbe73cb8 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/InteractionHelper.cs @@ -0,0 +1,158 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. +// All other rights reserved. + +using System.Diagnostics; +using System.Windows; +using System.Windows.Controls; + +namespace GitHub.UI +{ + /// + /// The InteractionHelper provides controls with support for all of the + /// common interactions like mouse movement, mouse clicks, key presses, + /// etc., and also incorporates proper event semantics when the control is + /// disabled. + /// + internal sealed class InteractionHelper + { + /// + /// Gets the control the InteractionHelper is targeting. + /// + public Control Control { get; private set; } + + /// + /// Gets a value indicating whether the control has focus. + /// + public bool IsFocused { get; private set; } + + /// + /// Gets a value indicating whether the mouse is over the control. + /// + public bool IsMouseOver { get; private set; } + + /// + /// Gets a value indicating whether the mouse button is pressed down + /// over the control. + /// + public bool IsPressed { get; private set; } + + /// + /// Reference used to call UpdateVisualState on the base class. + /// + private readonly IUpdateVisualState updateVisualState; + + /// + /// Initializes a new instance of the InteractionHelper class. + /// + /// Control receiving interaction. + public InteractionHelper(Control control) + { + Debug.Assert(control != null, "control should not be null!"); + Control = control; + updateVisualState = control as IUpdateVisualState; + + // Wire up the event handlers for events without a virtual override + control.Loaded += OnLoaded; + control.IsEnabledChanged += OnIsEnabledChanged; + } + + /// + /// Update the visual state of the control. + /// + /// + /// A value indicating whether to automatically generate transitions to + /// the new state, or instantly transition to the new state. + /// + /// + /// UpdateVisualState works differently than the rest of the injected + /// functionality. Most of the other events are overridden by the + /// calling class which calls Allow, does what it wants, and then calls + /// Base. UpdateVisualState is the opposite because a number of the + /// methods in InteractionHelper need to trigger it in the calling + /// class. We do this using the IUpdateVisualState internal interface. + /// + private void UpdateVisualState(bool useTransitions) + { + if (updateVisualState != null) + { + updateVisualState.UpdateVisualState(useTransitions); + } + } + + /// + /// Update the visual state of the control. + /// + /// + /// A value indicating whether to automatically generate transitions to + /// the new state, or instantly transition to the new state. + /// + public void UpdateVisualStateBase(bool useTransitions) + { + // Handle the Common states + if (!Control.IsEnabled) + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateDisabled, VisualStates.StateNormal); + } + else if (IsPressed) + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StatePressed, VisualStates.StateMouseOver, VisualStates.StateNormal); + } + else if (IsMouseOver) + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateMouseOver, VisualStates.StateNormal); + } + else + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateNormal); + } + + // Handle the Focused states + if (IsFocused) + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateFocused, VisualStates.StateUnfocused); + } + else + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateUnfocused); + } + } + + /// + /// Handle the control's Loaded event. + /// + /// The control. + /// Event arguments. + private void OnLoaded(object sender, RoutedEventArgs e) + { + UpdateVisualState(false); + } + + /// + /// Handle changes to the control's IsEnabled property. + /// + /// The control. + /// Event arguments. + private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + bool enabled = (bool)e.NewValue; + if (!enabled) + { + IsPressed = false; + IsMouseOver = false; + IsFocused = false; + } + + UpdateVisualState(true); + } + + /// + /// Update the visual state of the control when its template is changed. + /// + public void OnApplyTemplateBase() + { + UpdateVisualState(false); + } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/PopupHelper.cs b/src/GitHub.UI/Controls/AutoCompleteBox/PopupHelper.cs new file mode 100644 index 0000000000..7ba010c413 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/PopupHelper.cs @@ -0,0 +1,280 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. +// All other rights reserved. + +using System; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using GitHub.Extensions; +using GitHub.Helpers; +using GitHub.UI.Controls; + +namespace GitHub.UI +{ + /// + /// PopupHelper is a simple wrapper type that helps abstract platform + /// differences out of the Popup. + /// + internal class PopupHelper + { + /// + /// Gets a value indicating whether a visual popup state is being used + /// in the current template for the Closed state. Setting this value to + /// true will delay the actual setting of Popup.IsOpen to false until + /// after the visual state's transition for Closed is complete. + /// + public bool UsesClosingVisualState { get; private set; } + + /// + /// Gets or sets the parent control. + /// + private Control Parent { get; set; } + + /// + /// Gets or sets the maximum drop down height value. + /// + public double MaxDropDownHeight { get; set; } + + /// + /// Gets the Popup control instance. + /// + public Popup Popup { get; private set; } + + /// + /// Gets or sets a value indicating whether the actual Popup is open. + /// + public bool IsOpen + { + get { return Popup.IsOpen; } + set { Popup.IsOpen = value; } + } + + /// + /// Gets or sets the popup child framework element. Can be used if an + /// assumption is made on the child type. + /// + private FrameworkElement PopupChild { get; set; } + + /// + /// The Closed event is fired after the Popup closes. + /// + public event EventHandler Closed; + + /// + /// Fired when the popup children have a focus event change, allows the + /// parent control to update visual states or react to the focus state. + /// + public event EventHandler FocusChanged; + + /// + /// Fired when the popup children intercept an event that may indicate + /// the need for a visual state update by the parent control. + /// + public event EventHandler UpdateVisualStates; + + /// + /// Initializes a new instance of the PopupHelper class. + /// + /// The parent control. + public PopupHelper(Control parent) + { + Guard.ArgumentNotNull(parent, "parent"); + Parent = parent; + } + + /// + /// Initializes a new instance of the PopupHelper class. + /// + /// The parent control. + /// The Popup template part. + public PopupHelper(Control parent, Popup popup) + : this(parent) + { + Guard.ArgumentNotNull(parent, "parent"); + Guard.ArgumentNotNull(popup, "popup"); + + Popup = popup; + + var target = parent as IPopupTarget; + if (target != null) + { + popup.CustomPopupPlacementCallback += (size, targetSize, offset) => new[] + { + new CustomPopupPlacement(target.PopupPosition, PopupPrimaryAxis.Horizontal) + }; + } + } + + /// + /// Fires the Closed event. + /// + /// The event data. + private void OnClosed(EventArgs e) + { + var handler = Closed; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Actually closes the popup after the VSM state animation completes. + /// + /// Event source. + /// Event arguments. + private void OnPopupClosedStateChanged(object sender, VisualStateChangedEventArgs e) + { + // Delayed closing of the popup until now + if (e != null && e.NewState != null && e.NewState.Name == VisualStates.StatePopupClosed) + { + if (Popup != null) + { + Popup.IsOpen = false; + } + OnClosed(EventArgs.Empty); + } + } + + /// + /// Should be called by the parent control before the base + /// OnApplyTemplate method is called. + /// + public void BeforeOnApplyTemplate() + { + if (UsesClosingVisualState) + { + // Unhook the event handler for the popup closed visual state group. + // This code is used to enable visual state transitions before + // actually setting the underlying Popup.IsOpen property to false. + VisualStateGroup groupPopupClosed = VisualStates.TryGetVisualStateGroup(Parent, VisualStates.GroupPopup); + if (null != groupPopupClosed) + { + groupPopupClosed.CurrentStateChanged -= OnPopupClosedStateChanged; + UsesClosingVisualState = false; + } + } + + if (Popup != null) + { + Popup.Closed -= Popup_Closed; + } + } + + /// + /// Should be called by the parent control after the base + /// OnApplyTemplate method is called. + /// + public void AfterOnApplyTemplate() + { + if (Popup != null) + { + Popup.Closed += Popup_Closed; + } + + var groupPopupClosed = VisualStates.TryGetVisualStateGroup(Parent, VisualStates.GroupPopup); + if (null != groupPopupClosed) + { + groupPopupClosed.CurrentStateChanged += OnPopupClosedStateChanged; + UsesClosingVisualState = true; + } + + // TODO: Consider moving to the DropDownPopup setter + // TODO: Although in line with other implementations, what happens + // when the template is swapped out? + if (Popup != null) + { + PopupChild = Popup.Child as FrameworkElement; + + if (PopupChild != null) + { + PopupChild.MinWidth = 203; // TODO: Make this configurable. + PopupChild.GotFocus += PopupChild_GotFocus; + PopupChild.LostFocus += PopupChild_LostFocus; + PopupChild.MouseEnter += PopupChild_MouseEnter; + PopupChild.MouseLeave += PopupChild_MouseLeave; + } + } + } + + /// + /// Connected to the Popup Closed event and fires the Closed event. + /// + /// The source object. + /// The event data. + private void Popup_Closed(object sender, EventArgs e) + { + OnClosed(EventArgs.Empty); + } + + /// + /// Connected to several events that indicate that the FocusChanged + /// event should bubble up to the parent control. + /// + /// The event data. + private void OnFocusChanged(EventArgs e) + { + EventHandler handler = FocusChanged; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Fires the UpdateVisualStates event. + /// + /// The event data. + private void OnUpdateVisualStates(EventArgs e) + { + EventHandler handler = UpdateVisualStates; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// The popup child has received focus. + /// + /// The source object. + /// The event data. + private void PopupChild_GotFocus(object sender, RoutedEventArgs e) + { + OnFocusChanged(EventArgs.Empty); + } + + /// + /// The popup child has lost focus. + /// + /// The source object. + /// The event data. + private void PopupChild_LostFocus(object sender, RoutedEventArgs e) + { + OnFocusChanged(EventArgs.Empty); + } + + /// + /// The popup child has had the mouse enter its bounds. + /// + /// The source object. + /// The event data. + private void PopupChild_MouseEnter(object sender, MouseEventArgs e) + { + OnUpdateVisualStates(EventArgs.Empty); + } + + /// + /// The mouse has left the popup child's bounds. + /// + /// The source object. + /// The event data. + private void PopupChild_MouseLeave(object sender, MouseEventArgs e) + { + OnUpdateVisualStates(EventArgs.Empty); + } + } +} diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/RichTextBoxAutoCompleteTextInput.cs b/src/GitHub.UI/Controls/AutoCompleteBox/RichTextBoxAutoCompleteTextInput.cs new file mode 100644 index 0000000000..67def11de8 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/RichTextBoxAutoCompleteTextInput.cs @@ -0,0 +1,201 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.IO; +using System.Reactive; +using System.Reactive.Linq; +using System.Text; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Documents; +using System.Windows.Input; +using System.Windows.Markup; + +namespace GitHub.UI +{ + [ContentProperty("TextBox")] + public class RichTextBoxAutoCompleteTextInput : IAutoCompleteTextInput + { + private static readonly int newLineLength = Environment.NewLine.Length; + const int promptRichTextBoxCaretIndexAdjustments = 2; + RichTextBox textBox; + + public event PropertyChangedEventHandler PropertyChanged; + + TextPointer ContentStart + { + get { return textBox.Document.ContentStart; } + } + + TextPointer ContentEnd + { + get + { + // RichTextBox always appends a new line at the end. So we need to back that shit up. + return textBox.Document.ContentEnd.GetPositionAtOffset(-1 * newLineLength) + ?? textBox.Document.ContentEnd; + } + } + + public void Select(int position, int length) + { + var textRange = new TextRange(ContentStart, ContentEnd); + + if (textRange.Text.Length >= (position + length)) + { + var start = textRange.Start.GetPositionAtOffset(GetOffsetIndex(position), LogicalDirection.Forward); + var end = textRange.Start.GetPositionAtOffset(GetOffsetIndex(position + length), LogicalDirection.Backward); + if (start != null && end != null) + textBox.Selection.Select(start, end); + } + } + + public void SelectAll() + { + textBox.Selection.Select(ContentStart, ContentEnd); + } + + public int CaretIndex + { + get + { + var start = ContentStart; + var caret = textBox.CaretPosition; + var range = new TextRange(start, caret); + return range.Text.Length; + } + set + { + Select(value, 0); + Debug.Assert(value == CaretIndex, + String.Format(CultureInfo.InvariantCulture, + "I just set the caret index to '{0}' but it's '{1}'", value, CaretIndex)); + } + } + + public int SelectionStart + { + get + { + return new TextRange(ContentStart, textBox.Selection.Start).Text.Length; + } + } + + public int SelectionLength + { + get { return CaretIndex - SelectionStart; } + } + +#if DEBUG + [SuppressMessage("Microsoft.Reliability", "CA2000:Dispose objects before losing scope")] +#endif + public string Text + { + get + { + return new TextRange(ContentStart, ContentEnd).Text; + } + set + { + textBox.Document.Blocks.Clear(); + + if (!string.IsNullOrEmpty(value)) + { + using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(value))) + { + var contents = new TextRange(ContentStart, ContentEnd); + contents.Load(stream, DataFormats.Text); + } + } + } + } + + public IObservable> PreviewKeyDown + { + get; + private set; + } + + public IObservable> SelectionChanged { get; private set; } + + public IObservable> TextChanged { get; private set; } + + public UIElement Control { get { return textBox; } } + + public Point GetPositionFromCharIndex(int charIndex) + { + var offset = new TextRange(ContentStart, textBox.CaretPosition) + .Start + .GetPositionAtOffset(charIndex, LogicalDirection.Forward); + + return offset != null + ? offset.GetCharacterRect(LogicalDirection.Forward).BottomLeft + : new Point(0, 0); + } + + public Thickness Margin + { + get { return textBox.Margin; } + set { textBox.Margin = value; } + } + + public void Focus() + { + Keyboard.Focus(textBox); + } + + public RichTextBox TextBox + { + get + { + return textBox; + } + set + { + if (value != textBox) + { + textBox = value; + + PreviewKeyDown = Observable.FromEventPattern( + h => textBox.PreviewKeyDown += h, + h => textBox.PreviewKeyDown -= h); + + SelectionChanged = Observable.FromEventPattern( + h => textBox.SelectionChanged += h, + h => textBox.SelectionChanged -= h); + + TextChanged = Observable.FromEventPattern( + h => textBox.TextChanged += h, + h => textBox.TextChanged -= h); + + NotifyPropertyChanged("Control"); + } + + } + } + + // This is a fudge factor needed because of PromptRichTextBox. When commit messages are 51 characters or more, + // The PromptRichTextBox applies a styling that fucks up the CaretPosition by 2. :( + // This method helps us account for that. + int GetOffsetIndex(int selectionEnd) + { + if (textBox is PromptRichTextBox && selectionEnd >= PromptRichTextBox.BadCommitMessageLength) + { + return selectionEnd + promptRichTextBoxCaretIndexAdjustments; + } + return selectionEnd; + } + + private void NotifyPropertyChanged(String info) + { + var propertyChanged = PropertyChanged; + if (propertyChanged != null) + { + propertyChanged(this, new PropertyChangedEventArgs(info)); + } + } + } +} diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/SelectorSelectionAdapter.cs b/src/GitHub.UI/Controls/AutoCompleteBox/SelectorSelectionAdapter.cs new file mode 100644 index 0000000000..e3d2aab6b0 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/SelectorSelectionAdapter.cs @@ -0,0 +1,354 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. +// All other rights reserved. + +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Automation.Peers; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; + +namespace GitHub.UI +{ + /// + /// Represents the selection adapter contained in the drop-down portion of + /// an control. + /// + /// Stable + public class SelectorSelectionAdapter : ISelectionAdapter + { + /// + /// The Selector instance. + /// + private Selector selector; + + /// + /// Gets or sets a value indicating whether the selection change event + /// should not be fired. + /// + private bool IgnoringSelectionChanged { get; set; } + + /// + /// Gets or sets the underlying control. + /// + /// The underlying control. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", + Justification = "We do validate the parameter. Code Analysis just doesn't see it.")] + public Selector SelectorControl + { + get { return selector; } + + set + { + if (selector != null) + { + selector.SelectionChanged -= OnSelectionChanged; + selector.MouseLeftButtonUp -= OnSelectorMouseLeftButtonUp; + } + + selector = value; + + if (selector != null) + { + selector.SelectionChanged += OnSelectionChanged; + selector.MouseLeftButtonUp += OnSelectorMouseLeftButtonUp; + } + } + } + + /// + /// Occurs when the property value changes. + /// + public event SelectionChangedEventHandler SelectionChanged; + + /// + /// Occurs when an item is selected and is committed to the underlying + /// control. + /// + public event RoutedEventHandler Commit; + + /// + /// Occurs when a selection is canceled before it is committed. + /// + public event RoutedEventHandler Cancel; + + /// + /// Initializes a new instance of the class. + /// + public SelectorSelectionAdapter() + { + } + + /// + /// Initializes a new instance of the class with the specified + /// + /// control. + /// + /// The + /// control + /// to wrap as a + /// . + public SelectorSelectionAdapter(Selector selector) + { + SelectorControl = selector; + } + + /// + /// Gets or sets the selected item of the selection adapter. + /// + /// The selected item of the underlying selection adapter. + public object SelectedItem + { + get + { + return SelectorControl == null ? null : SelectorControl.SelectedItem; + } + + set + { + IgnoringSelectionChanged = true; + if (SelectorControl != null) + { + SelectorControl.SelectedItem = value; + } + + // Attempt to reset the scroll viewer's position + if (value == null) + { + ResetScrollViewer(); + } + + IgnoringSelectionChanged = false; + } + } + + /// + /// Gets or sets a collection that is used to generate the content of + /// the selection adapter. + /// + /// The collection used to generate content for the selection + /// adapter. + public IEnumerable ItemsSource + { + get + { + return SelectorControl == null ? null : SelectorControl.ItemsSource; + } + set + { + if (SelectorControl != null) + { + SelectorControl.ItemsSource = value; + } + } + } + + /// + /// If the control contains a ScrollViewer, this will reset the viewer + /// to be scrolled to the top. + /// + private void ResetScrollViewer() + { + if (SelectorControl != null) + { + var sv = SelectorControl.GetLogicalChildrenBreadthFirst().OfType().FirstOrDefault(); + if (sv != null) + { + sv.ScrollToTop(); + } + } + } + + /// + /// Handles the mouse left button up event on the selector control. + /// + /// The source object. + /// The event data. + private void OnSelectorMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + OnCommit(); + } + + /// + /// Handles the SelectionChanged event on the Selector control. + /// + /// The source object. + /// The selection changed event data. + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (IgnoringSelectionChanged) + { + return; + } + + var handler = SelectionChanged; + if (handler != null) + { + handler(sender, e); + } + } + + /// + /// Increments the + /// + /// property of the underlying + /// + /// control. + /// + protected void SelectedIndexIncrement() + { + if (SelectorControl != null) + { + SelectorControl.SelectedIndex = + SelectorControl.SelectedIndex + 1 >= SelectorControl.Items.Count + ? SelectorControl.Items.Count - 1 + : SelectorControl.SelectedIndex + 1; + } + } + + /// + /// Decrements the + /// + /// property of the underlying + /// + /// control. + /// + protected void SelectedIndexDecrement() + { + if (SelectorControl != null) + { + int index = SelectorControl.SelectedIndex; + if (index >= 1) + { + SelectorControl.SelectedIndex--; + } + else + { + SelectorControl.SelectedIndex = 0; + } + } + } + + /// + /// Provides handling for the + /// event that occurs + /// when a key is pressed while the drop-down portion of the + /// has focus. + /// + /// A + /// that contains data about the + /// event. + public void HandleKeyDown(KeyEventArgs e) + { + switch (e.Key) + { + case Key.Enter: + case Key.Tab: + case Key.Right: + OnCommit(); + e.Handled = true; + break; + + case Key.Up: + SelectedIndexDecrement(); + e.Handled = true; + break; + + case Key.Down: + if ((ModifierKeys.Alt & Keyboard.Modifiers) == ModifierKeys.None) + { + SelectedIndexIncrement(); + e.Handled = true; + } + break; + + case Key.Escape: + OnCancel(); + e.Handled = true; + break; + } + } + + /// + /// Raises the + /// + /// event. + /// + protected virtual void OnCommit() + { + OnCommit(this, new RoutedEventArgs()); + } + + /// + /// Fires the Commit event. + /// + /// The source object. + /// The event data. + private void OnCommit(object sender, RoutedEventArgs e) + { + RoutedEventHandler handler = Commit; + if (handler != null) + { + handler(sender, e); + } + + AfterAdapterAction(); + } + + /// + /// Raises the + /// + /// event. + /// + protected virtual void OnCancel() + { + OnCancel(this, new RoutedEventArgs()); + } + + /// + /// Fires the Cancel event. + /// + /// The source object. + /// The event data. + private void OnCancel(object sender, RoutedEventArgs e) + { + var handler = Cancel; + if (handler != null) + { + handler(sender, e); + } + + AfterAdapterAction(); + } + + /// + /// Change the selection after the actions are complete. + /// + private void AfterAdapterAction() + { + IgnoringSelectionChanged = true; + if (SelectorControl != null) + { + SelectorControl.SelectedItem = null; + SelectorControl.SelectedIndex = -1; + } + IgnoringSelectionChanged = false; + } + + /// + /// Returns an automation peer for the underlying + /// + /// control, for use by the Silverlight automation infrastructure. + /// + /// An automation peer for use by the Silverlight automation + /// infrastructure. + public AutomationPeer CreateAutomationPeer() + { + return selector != null ? UIElementAutomationPeer.CreatePeerForElement(selector) : null; + } + } +} diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/TextBoxAutoCompleteTextInput.cs b/src/GitHub.UI/Controls/AutoCompleteBox/TextBoxAutoCompleteTextInput.cs new file mode 100644 index 0000000000..3b46c0a877 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/TextBoxAutoCompleteTextInput.cs @@ -0,0 +1,121 @@ +using System; +using System.ComponentModel; +using System.Reactive; +using System.Reactive.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Markup; +using ReactiveUI.Wpf; +using ReactiveUI; + +namespace GitHub.UI +{ + [ContentProperty("TextBox")] + public class TextBoxAutoCompleteTextInput : IAutoCompleteTextInput + { + TextBox textBox; + + public event PropertyChangedEventHandler PropertyChanged; + + public void Select(int position, int length) + { + textBox.Select(position, length); + } + + public void SelectAll() + { + textBox.SelectAll(); + } + + public int CaretIndex + { + get { return textBox.CaretIndex; } + set { textBox.CaretIndex = value; } + } + + public int SelectionStart + { + get { return textBox.SelectionStart; } + set { textBox.SelectionStart = value; } + } + + public int SelectionLength + { + get { return textBox.SelectionLength; } + } + + public string Text + { + get { return textBox.Text; } + set { textBox.Text = value; } + } + + public IObservable> PreviewKeyDown + { + get; + private set; + } + + public IObservable> SelectionChanged { get; private set; } + public IObservable> TextChanged { get; private set; } + public UIElement Control { get { return textBox; } } + + public Point GetPositionFromCharIndex(int charIndex) + { + var position = textBox.GetRectFromCharacterIndex(charIndex).BottomLeft; + position.Offset(0, 10); // Vertically pad it. Yeah, Point is mutable. WTF? + return position; + } + + public void Focus() + { + Keyboard.Focus(textBox); + } + + public TextBox TextBox + { + get + { + return textBox; + } + set + { + if (value != textBox) + { + textBox = value; + + PreviewKeyDown = Observable.FromEventPattern( + h => textBox.PreviewKeyDown += h, + h => textBox.PreviewKeyDown -= h); + + SelectionChanged = Observable.FromEventPattern( + h => textBox.SelectionChanged += h, + h => textBox.SelectionChanged -= h); + + TextChanged = Observable.FromEventPattern( + h => textBox.TextChanged += h, + h => textBox.TextChanged -= h); + + NotifyPropertyChanged("Control"); + } + } + } + + public Thickness Margin + { + get { return textBox.Margin; } + set { textBox.Margin = value; } + } + + private void NotifyPropertyChanged(String info) + { + var propertyChanged = PropertyChanged; + if (propertyChanged != null) + { + propertyChanged(this, new PropertyChangedEventArgs(info)); + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/VisualStates.cs b/src/GitHub.UI/Controls/AutoCompleteBox/VisualStates.cs new file mode 100644 index 0000000000..6aa065fdaf --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/VisualStates.cs @@ -0,0 +1,409 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. +// All other rights reserved. + +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace GitHub.UI +{ + /// + /// Names and helpers for visual states in the controls. + /// + internal static class VisualStates + { + #region GroupCommon + /// + /// Common state group. + /// + public const string GroupCommon = "CommonStates"; + + /// + /// Normal state of the Common state group. + /// + public const string StateNormal = "Normal"; + + /// + /// Normal state of the Common state group. + /// + public const string StateReadOnly = "ReadOnly"; + + /// + /// MouseOver state of the Common state group. + /// + public const string StateMouseOver = "MouseOver"; + + /// + /// Pressed state of the Common state group. + /// + public const string StatePressed = "Pressed"; + + /// + /// Disabled state of the Common state group. + /// + public const string StateDisabled = "Disabled"; + #endregion GroupCommon + + #region GroupFocus + /// + /// Focus state group. + /// + public const string GroupFocus = "FocusStates"; + + /// + /// Unfocused state of the Focus state group. + /// + public const string StateUnfocused = "Unfocused"; + + /// + /// Focused state of the Focus state group. + /// + public const string StateFocused = "Focused"; + #endregion GroupFocus + + #region GroupSelection + /// + /// Selection state group. + /// + public const string GroupSelection = "SelectionStates"; + + /// + /// Selected state of the Selection state group. + /// + public const string StateSelected = "Selected"; + + /// + /// Unselected state of the Selection state group. + /// + public const string StateUnselected = "Unselected"; + + /// + /// Selected inactive state of the Selection state group. + /// + public const string StateSelectedInactive = "SelectedInactive"; + #endregion GroupSelection + + #region GroupExpansion + /// + /// Expansion state group. + /// + public const string GroupExpansion = "ExpansionStates"; + + /// + /// Expanded state of the Expansion state group. + /// + public const string StateExpanded = "Expanded"; + + /// + /// Collapsed state of the Expansion state group. + /// + public const string StateCollapsed = "Collapsed"; + #endregion GroupExpansion + + #region GroupPopup + /// + /// Popup state group. + /// + public const string GroupPopup = "PopupStates"; + + /// + /// Opened state of the Popup state group. + /// + public const string StatePopupOpened = "PopupOpened"; + + /// + /// Closed state of the Popup state group. + /// + public const string StatePopupClosed = "PopupClosed"; + #endregion + + #region GroupValidation + /// + /// ValidationStates state group. + /// + public const string GroupValidation = "ValidationStates"; + + /// + /// The valid state for the ValidationStates group. + /// + public const string StateValid = "Valid"; + + /// + /// Invalid, focused state for the ValidationStates group. + /// + public const string StateInvalidFocused = "InvalidFocused"; + + /// + /// Invalid, unfocused state for the ValidationStates group. + /// + public const string StateInvalidUnfocused = "InvalidUnfocused"; + #endregion + + #region GroupExpandDirection + /// + /// ExpandDirection state group. + /// + public const string GroupExpandDirection = "ExpandDirectionStates"; + + /// + /// Down expand direction state of ExpandDirection state group. + /// + public const string StateExpandDown = "ExpandDown"; + + /// + /// Up expand direction state of ExpandDirection state group. + /// + public const string StateExpandUp = "ExpandUp"; + + /// + /// Left expand direction state of ExpandDirection state group. + /// + public const string StateExpandLeft = "ExpandLeft"; + + /// + /// Right expand direction state of ExpandDirection state group. + /// + public const string StateExpandRight = "ExpandRight"; + #endregion + + #region GroupHasItems + /// + /// HasItems state group. + /// + public const string GroupHasItems = "HasItemsStates"; + + /// + /// HasItems state of the HasItems state group. + /// + public const string StateHasItems = "HasItems"; + + /// + /// NoItems state of the HasItems state group. + /// + public const string StateNoItems = "NoItems"; + #endregion GroupHasItems + + #region GroupIncrease + /// + /// Increment state group. + /// + public const string GroupIncrease = "IncreaseStates"; + + /// + /// State enabled for increment group. + /// + public const string StateIncreaseEnabled = "IncreaseEnabled"; + + /// + /// State disabled for increment group. + /// + public const string StateIncreaseDisabled = "IncreaseDisabled"; + #endregion GroupIncrease + + #region GroupDecrease + /// + /// Decrement state group. + /// + public const string GroupDecrease = "DecreaseStates"; + + /// + /// State enabled for decrement group. + /// + public const string StateDecreaseEnabled = "DecreaseEnabled"; + + /// + /// State disabled for decrement group. + /// + public const string StateDecreaseDisabled = "DecreaseDisabled"; + #endregion GroupDecrease + + #region GroupIteractionMode + /// + /// InteractionMode state group. + /// + public const string GroupInteractionMode = "InteractionModeStates"; + + /// + /// Edit of the DisplayMode state group. + /// + public const string StateEdit = "Edit"; + + /// + /// Display of the DisplayMode state group. + /// + public const string StateDisplay = "Display"; + #endregion GroupIteractionMode + + #region GroupLocked + /// + /// DisplayMode state group. + /// + public const string GroupLocked = "LockedStates"; + + /// + /// Edit of the DisplayMode state group. + /// + public const string StateLocked = "Locked"; + + /// + /// Display of the DisplayMode state group. + /// + public const string StateUnlocked = "Unlocked"; + #endregion GroupLocked + + #region GroupActive + /// + /// Active state. + /// + public const string StateActive = "Active"; + + /// + /// Inactive state. + /// + public const string StateInactive = "Inactive"; + + /// + /// Active state group. + /// + public const string GroupActive = "ActiveStates"; + #endregion GroupActive + + #region GroupWatermark + /// + /// Non-watermarked state. + /// + public const string StateUnwatermarked = "Unwatermarked"; + + /// + /// Watermarked state. + /// + public const string StateWatermarked = "Watermarked"; + + /// + /// Watermark state group. + /// + public const string GroupWatermark = "WatermarkStates"; + #endregion GroupWatermark + + #region GroupCalendarButtonFocus + /// + /// Unfocused state for Calendar Buttons. + /// + public const string StateCalendarButtonUnfocused = "CalendarButtonUnfocused"; + + /// + /// Focused state for Calendar Buttons. + /// + public const string StateCalendarButtonFocused = "CalendarButtonFocused"; + + /// + /// CalendarButtons Focus state group. + /// + public const string GroupCalendarButtonFocus = "CalendarButtonFocusStates"; + #endregion GroupCalendarButtonFocus + + #region GroupBusyStatus + /// + /// Busy state for BusyIndicator. + /// + public const string StateBusy = "Busy"; + + /// + /// Idle state for BusyIndicator. + /// + public const string StateIdle = "Idle"; + + /// + /// Busyness group name. + /// + public const string GroupBusyStatus = "BusyStatusStates"; + #endregion + + #region GroupVisibility + /// + /// Visible state name for BusyIndicator. + /// + public const string StateVisible = "Visible"; + + /// + /// Hidden state name for BusyIndicator. + /// + public const string StateHidden = "Hidden"; + + /// + /// BusyDisplay group. + /// + public const string GroupVisibility = "VisibilityStates"; + #endregion + + /// + /// Use VisualStateManager to change the visual state of the control. + /// + /// + /// Control whose visual state is being changed. + /// + /// + /// A value indicating whether to use transitions when updating the + /// visual state, or to snap directly to the new visual state. + /// + /// + /// Ordered list of state names and fallback states to transition into. + /// Only the first state to be found will be used. + /// + public static void GoToState(Control control, bool useTransitions, params string[] stateNames) + { + Debug.Assert(control != null, "control should not be null!"); + Debug.Assert(stateNames != null, "stateNames should not be null!"); + Debug.Assert(stateNames.Length > 0, "stateNames should not be empty!"); + + foreach (string name in stateNames) + { + if (VisualStateManager.GoToState(control, name, useTransitions)) + { + break; + } + } + } + + /// + /// Gets the implementation root of the Control. + /// + /// The DependencyObject. + /// + /// Implements Silverlight's corresponding internal property on Control. + /// + /// Returns the implementation root or null. + public static FrameworkElement GetImplementationRoot(DependencyObject dependencyObject) + { + Debug.Assert(dependencyObject != null, "DependencyObject should not be null."); + return (1 == VisualTreeHelper.GetChildrenCount(dependencyObject)) ? + VisualTreeHelper.GetChild(dependencyObject, 0) as FrameworkElement : + null; + } + + /// + /// This method tries to get the named VisualStateGroup for the + /// dependency object. The provided object's ImplementationRoot will be + /// looked up in this call. + /// + /// The dependency object. + /// The visual state group's name. + /// Returns null or the VisualStateGroup object. + public static VisualStateGroup TryGetVisualStateGroup(DependencyObject dependencyObject, string groupName) + { + var root = GetImplementationRoot(dependencyObject); + if (root == null) + { + return null; + } + + return VisualStateManager.GetVisualStateGroups(root) + .OfType() + .FirstOrDefault(group => string.CompareOrdinal(groupName, @group.Name) == 0); + } + } +} diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/VisualTreeExtensions.cs b/src/GitHub.UI/Controls/AutoCompleteBox/VisualTreeExtensions.cs new file mode 100644 index 0000000000..b6cc0966c5 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/VisualTreeExtensions.cs @@ -0,0 +1,71 @@ +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Media; + +namespace GitHub.UI +{ + public static class VisualTreeExtensions + { + /// + /// Retrieves all the visual children of a framework element. + /// + /// The parent framework element. + /// The visual children of the framework element. + internal static IEnumerable GetVisualChildren(this DependencyObject parent) + { + Debug.Assert(parent != null, "The parent cannot be null."); + + int childCount = VisualTreeHelper.GetChildrenCount(parent); + for (int counter = 0; counter < childCount; counter++) + { + yield return VisualTreeHelper.GetChild(parent, counter); + } + } + + /// + /// Retrieves all the logical children of a framework element using a + /// breadth-first search. A visual element is assumed to be a logical + /// child of another visual element if they are in the same namescope. + /// For performance reasons this method manually manages the queue + /// instead of using recursion. + /// + /// + /// License for this method. + /// + /// (c) Copyright Microsoft Corporation. + /// This source is subject to the Microsoft Public License (Ms-PL). + /// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. + /// All other rights reserved. + /// + /// The parent framework element. + /// The logical children of the framework element. + internal static IEnumerable GetLogicalChildrenBreadthFirst(this FrameworkElement parent) + { + Debug.Assert(parent != null, "The parent cannot be null."); + + var queue = new Queue(parent.GetVisualChildren().OfType()); + + while (queue.Count > 0) + { + var element = queue.Dequeue(); + yield return element; + + foreach (var visualChild in element.GetVisualChildren().OfType()) + { + queue.Enqueue(visualChild); + } + } + } + + internal static Window GetActiveWindow(this Application application) + { + var windows = application.Windows; + if (windows.Count == 0) return null; + return windows.Count == 1 + ? windows[0] // Optimization. I think this is the common case for us. + : windows.Cast().FirstOrDefault(x => x.IsActive); + } + } +} diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/WeakEventListener.cs b/src/GitHub.UI/Controls/AutoCompleteBox/WeakEventListener.cs new file mode 100644 index 0000000000..f9a76e5581 --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/WeakEventListener.cs @@ -0,0 +1,84 @@ +// (c) Copyright Microsoft Corporation. +// This source is subject to the Microsoft Public License (Ms-PL). +// Please see http://go.microsoft.com/fwlink/?LinkID=131993] for details. +// All other rights reserved. + +using System; +using System.Diagnostics.CodeAnalysis; + +namespace GitHub.UI +{ + /// + /// Implements a weak event listener that allows the owner to be garbage + /// collected if its only remaining link is an event handler. + /// + /// Type of instance listening for the event. + /// Type of source for the event. + /// Type of event arguments for the event. + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Used as link target in several projects.")] + internal class WeakEventListener where TInstance : class + { + /// + /// WeakReference to the instance listening for the event. + /// + private readonly WeakReference weakInstance; + + /// + /// Gets or sets the method to call when the event fires. + /// + public Action OnEventAction { get; set; } + + /// + /// Gets or sets the method to call when detaching from the event. + /// + public Action> OnDetachAction { get; set; } + + /// + /// Initializes a new instances of the WeakEventListener class. + /// + /// Instance subscribing to the event. + public WeakEventListener(TInstance instance) + { + if (null == instance) + { + throw new ArgumentNullException("instance"); + } + weakInstance = new WeakReference(instance); + } + + /// + /// Handler for the subscribed event calls OnEventAction to handle it. + /// + /// Event source. + /// Event arguments. + public void OnEvent(TSource source, TEventArgs eventArgs) + { + var target = (TInstance)weakInstance.Target; + if (null != target) + { + // Call registered action + if (null != OnEventAction) + { + OnEventAction(target, source, eventArgs); + } + } + else + { + // Detach from event + Detach(); + } + } + + /// + /// Detaches from the subscribed event. + /// + public void Detach() + { + if (null != OnDetachAction) + { + OnDetachAction(this); + OnDetachAction = null; + } + } + } +} \ No newline at end of file diff --git a/src/GitHub.UI/Controls/AutoCompleteBox/_README.md b/src/GitHub.UI/Controls/AutoCompleteBox/_README.md new file mode 100644 index 0000000000..c601dbfeda --- /dev/null +++ b/src/GitHub.UI/Controls/AutoCompleteBox/_README.md @@ -0,0 +1,40 @@ +# WPF Toolkit + +This folder contains code copied and adapted from the [WPF Toolkit](http://wpf.codeplex.com/) project under the MS-PL +license. + +This contains the AutoCompleteBox code. + +# LICENSE + +Microsoft Public License (Ms-PL) + +This license governs use of the accompanying software. If you use the software, you accept this license. If you do not accept the license, do not use the software. + +1. Definitions + +The terms "reproduce," "reproduction," "derivative works," and "distribution" have the same meaning here as under U.S. copyright law. + +A "contribution" is the original software, or any additions or changes to the software. + +A "contributor" is any person that distributes its contribution under this license. + +"Licensed patents" are a contributor's patent claims that read directly on its contribution. + +2. Grant of Rights + +(A) Copyright Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free copyright license to reproduce its contribution, prepare derivative works of its contribution, and distribute its contribution or any derivative works that you create. + +(B) Patent Grant- Subject to the terms of this license, including the license conditions and limitations in section 3, each contributor grants you a non-exclusive, worldwide, royalty-free license under its licensed patents to make, have made, use, sell, offer for sale, import, and/or otherwise dispose of its contribution in the software or derivative works of the contribution in the software. + +3. Conditions and Limitations + +(A) No Trademark License- This license does not grant you rights to use any contributors' name, logo, or trademarks. + +(B) If you bring a patent claim against any contributor over patents that you claim are infringed by the software, your patent license from such contributor to the software ends automatically. + +(C) If you distribute any portion of the software, you must retain all copyright, patent, trademark, and attribution notices that are present in the software. + +(D) If you distribute any portion of the software in source code form, you may do so only under this license by including a complete copy of this license with your distribution. If you distribute any portion of the software in compiled or object code form, you may only do so under a license that complies with this license. + +(E) The software is licensed "as-is." You bear the risk of using it. The contributors give no express warranties, guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot change. To the extent permitted under your local laws, the contributors exclude the implied warranties of merchantability, fitness for a particular purpose and non-infringement. \ No newline at end of file diff --git a/src/GitHub.UI/Controls/Buttons/OcticonButton.cs b/src/GitHub.UI/Controls/Buttons/OcticonButton.cs index 6ecd689870..6d963995a2 100644 --- a/src/GitHub.UI/Controls/Buttons/OcticonButton.cs +++ b/src/GitHub.UI/Controls/Buttons/OcticonButton.cs @@ -21,7 +21,7 @@ public double IconRotationAngle set { SetValue(IconRotationAngleProperty, value); } } - public static DependencyProperty DataProperty = + public static readonly DependencyProperty DataProperty = Path.DataProperty.AddOwner(typeof(OcticonButton)); public Geometry Data @@ -30,7 +30,7 @@ public Geometry Data set { SetValue(DataProperty, value); } } - public static DependencyProperty IconProperty = + public static readonly DependencyProperty IconProperty = OcticonPath.IconProperty.AddOwner( typeof(OcticonButton), new FrameworkPropertyMetadata(defaultValue: Octicon.mark_github, flags: diff --git a/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.cs b/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.cs deleted file mode 100644 index b40a6876c0..0000000000 --- a/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Windows; -using System.Windows.Media; -using System.Windows.Shapes; - -namespace GitHub.UI -{ - public class OcticonCircleButton : OcticonButton - { - public static readonly DependencyProperty ShowSpinnerProperty = DependencyProperty.Register( - "ShowSpinner", typeof(bool), typeof(OcticonCircleButton)); - - public static readonly DependencyProperty IconForegroundProperty = DependencyProperty.Register( - "IconForeground", typeof(Brush), typeof(OcticonCircleButton)); - - public static readonly DependencyProperty ActiveBackgroundProperty = DependencyProperty.Register( - "ActiveBackground", typeof(Brush), typeof(OcticonCircleButton)); - - public static readonly DependencyProperty ActiveForegroundProperty = DependencyProperty.Register( - "ActiveForeground", typeof(Brush), typeof(OcticonCircleButton)); - - public static readonly DependencyProperty PressedBackgroundProperty = DependencyProperty.Register( - "PressedBackground", typeof(Brush), typeof(OcticonCircleButton)); - - public static readonly DependencyProperty IconSizeProperty = DependencyProperty.Register( - "IconSize", typeof(double), typeof(OcticonCircleButton), new FrameworkPropertyMetadata(16d, - FrameworkPropertyMetadataOptions.AffectsArrange | - FrameworkPropertyMetadataOptions.AffectsMeasure | - FrameworkPropertyMetadataOptions.AffectsRender)); - - public bool ShowSpinner - { - get { return (bool)GetValue(ShowSpinnerProperty); } - set { SetValue(ShowSpinnerProperty, value); } - } - - public Brush IconForeground - { - get { return (Brush)GetValue(IconForegroundProperty); } - set { SetValue(IconForegroundProperty, value); } - } - - public Brush ActiveBackground - { - get { return (Brush)GetValue(ActiveBackgroundProperty); } - set { SetValue(ActiveBackgroundProperty, value); } - } - - public Brush ActiveForeground - { - get { return (Brush)GetValue(ActiveForegroundProperty); } - set { SetValue(ActiveForegroundProperty, value); } - } - - public Brush PressedBackground - { - get { return (Brush)GetValue(PressedBackgroundProperty); } - set { SetValue(PressedBackgroundProperty, value); } - } - - public double IconSize - { - get { return (double)GetValue(IconSizeProperty); } - set { SetValue(IconSizeProperty, value); } - } - - static OcticonCircleButton() - { - Path.DataProperty.AddOwner(typeof(OcticonCircleButton)); - } - - static void OnIconChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) - { - d.SetValue(Path.DataProperty, OcticonPath.GetGeometryForIcon((Octicon)e.NewValue)); - } - } -} diff --git a/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.xaml b/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.xaml deleted file mode 100644 index 09fa8bdb25..0000000000 --- a/src/GitHub.UI/Controls/Buttons/OcticonCircleButton.xaml +++ /dev/null @@ -1,298 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/CommonControlsCheckBoxStyle.xaml b/src/GitHub.VisualStudio.TestApp/Themes/CommonControlsCheckBoxStyle.xaml new file mode 100644 index 0000000000..77b700438f --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/CommonControlsCheckBoxStyle.xaml @@ -0,0 +1,105 @@ + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/CommonControlsComboBoxStyle.xaml b/src/GitHub.VisualStudio.TestApp/Themes/CommonControlsComboBoxStyle.xaml new file mode 100644 index 0000000000..dbd52c3208 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/CommonControlsComboBoxStyle.xaml @@ -0,0 +1,374 @@ + + + M 0 0 L 3 3 L 6 0 Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/CommonControlsTextBoxStyle.xaml b/src/GitHub.VisualStudio.TestApp/Themes/CommonControlsTextBoxStyle.xaml new file mode 100644 index 0000000000..1a1b1a4992 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/CommonControlsTextBoxStyle.xaml @@ -0,0 +1,34 @@ + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Dark/CommonControlsColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Dark/CommonControlsColors.xaml new file mode 100644 index 0000000000..7aa39f74b1 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Dark/CommonControlsColors.xaml @@ -0,0 +1,198 @@ + + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF555555 + #FF555555 + #FF007ACC + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF007ACC + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF656565 + #FF656565 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF1F1F20 + #FF1F1F20 + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF999999 + #FF999999 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF999999 + #FF999999 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF333337 + #FF333337 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF434346 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF999999 + #FF999999 + #FF333337 + #FF333337 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF656565 + #FF656565 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1F1F1C + #FF1F1F1C + #19000000 + #19000000 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF333337 + #FF333337 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #66007ACC + #66007ACC + #FFFFFFFF + #FFFFFFFF + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF555555 + #FF555555 + #FF555555 + #FF555555 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF333337 + #FF333337 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF434346 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FFFFFFFF + #FFFFFFFF + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Dark/EnvironmentColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Dark/EnvironmentColors.xaml new file mode 100644 index 0000000000..0259d3ca88 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Dark/EnvironmentColors.xaml @@ -0,0 +1,1921 @@ + + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FF000000 + #FF000000 + #FF999999 + #FF999999 + #80525252 + #80525252 + #FF656565 + #FF656565 + #80525252 + #80525252 + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E42 + #FF3E3E42 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF0097FB + #FF0097FB + #FFD0D0D0 + #FFD0D0D0 + #FF000000 + #FF000000 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF464646 + #FF464646 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF0F2F9 + #FFF0F2F9 + #FFD3DCEF + #FFD3DCEF + #FFCCCC66 + #FFCCCC66 + #FFFFFFCC + #FFFFFFCC + #FF000000 + #FF000000 + #FFD2D2D2 + #FFD2D2D2 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF00008B + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFF7F0F0 + #FFF7F0F0 + #FFEDDADC + #FFEDDADC + #FFFFFFFF + #FFFFFFFF + #FF0054E3 + #FF0054E3 + #FFDDD6EF + #FFDDD6EF + #FF266035 + #FF266035 + #FFFFFFFF + #FFFFFFFF + #FF716F64 + #FF716F64 + #FFF3F7F0 + #FFF3F7F0 + #FFE6F0DB + #FFE6F0DB + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFB0764F + #FFB0764F + #FF716F64 + #FF716F64 + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFD8D8D8 + #FFD8D8D8 + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFD6ECEF + #FFD6ECEF + #FFFF0000 + #FFFF0000 + #FFF8F4E9 + #FFF8F4E9 + #FFF0E9D2 + #FFF0E9D2 + #FF333337 + #FF333337 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF434346 + #FF434346 + #FF434346 + #FF434346 + #FF656565 + #FF656565 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF333337 + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FF656565 + #FF656565 + #FFF1F1F1 + #FFF1F1F1 + #FF46464A + #FF46464A + #FF46464A + #FF46464A + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3399FF + #FF3399FF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF333337 + #FF999999 + #FF999999 + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FF1B1B1C + #FF1B1B1C + #FF333334 + #FF333334 + #FFF1F1F1 + #FFF1F1F1 + #FF333334 + #FF333334 + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF333337 + #FF333337 + #FF007ACC + #FF007ACC + #FF999999 + #FF999999 + #FF333337 + #FF333337 + #FF999999 + #FF999999 + #FFFFFFFF + #FFFFFFFF + #FF999999 + #FF999999 + #FF0097FB + #FF0097FB + #FF999999 + #FF999999 + #FF999999 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #72555555 + #72555555 + #72555555 + #72555555 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF3399FF + #FF3399FF + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF434346 + #FF434346 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF333337 + #FF333337 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF222222 + #FF222222 + #FF46464A + #FF46464A + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FFFEFCC8 + #FFFEFCC8 + #FF555555 + #FF555555 + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF333337 + #FF333337 + #003F3F46 + #003F3F46 + #FF424245 + #FF424245 + #FF4D4D50 + #FF4D4D50 + #FF505051 + #FF505051 + #FFF1F1F1 + #FFF1F1F1 + #FF333337 + #FF333337 + #FFF1F1F1 + #FFF1F1F1 + #FF2C2C2F + #FF2C2C2F + #FF37373A + #FF37373A + #FF3D3D3F + #FF3D3D3F + #FF7A7A7A + #FF7A7A7A + #FF333337 + #FF333337 + #FF656565 + #FF656565 + #FF252526 + #FF252526 + #FF46464A + #FF46464A + #FF3F3F46 + #FF3F3F46 + #FF656565 + #FF656565 + #FFFFFFFF + #FFFFFFFF + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FFF2F4F8 + #FFF2F4F8 + #FF000000 + #FF000000 + #FF4A6184 + #FF4A6184 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFBCC7D8 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFF0F0F0 + #FFF0F0F0 + #FF000000 + #FF000000 + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF007ACC + #FF007ACC + #FF333337 + #FF333337 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF434346 + #FF434346 + #FF434346 + #FF434346 + #FF656565 + #FF656565 + #FF999999 + #FF999999 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF434346 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF333337 + #FFF1F1F1 + #FFF1F1F1 + #72000000 + #72000000 + #FF2D2D30 + #FF2D2D30 + #FF333337 + #FF333337 + #FF252526 + #FF252526 + #FF0097FB + #FF0097FB + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFFF8C00 + #FFFF8C00 + #FFFF8C00 + #FFFF8C00 + #FF656565 + #FF656565 + #FF656565 + #FF656565 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FFF1F1F1 + #FFF1F1F1 + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFFFFFFF + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFFFFFFF + #FF555555 + #FF555555 + #FF555555 + #FF555555 + #FFF1F1F1 + #FFF1F1F1 + #FF442359 + #FF442359 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FF442359 + #FF442359 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FFF1F1F1 + #FFF1F1F1 + #FFE1D3E4 + #FFE1D3E4 + #FFB064AB + #FFB064AB + #FFB064AB + #FFB064AB + #FFFFFFFF + #FFFFFFFF + #FF9B4F96 + #FF9B4F96 + #FF9B4F96 + #FF9B4F96 + #FFFFFFFF + #FFFFFFFF + #FF555555 + #FF555555 + #FF555555 + #FF555555 + #FFF1F1F1 + #FFF1F1F1 + #FF555555 + #FF555555 + #FFE1D3E4 + #FFE1D3E4 + #FF6D6D70 + #FF6D6D70 + #FFD0E6F5 + #FFD0E6F5 + #FF6D6D70 + #FF6D6D70 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF1C97EA + #FF1C97EA + #FFD0E6F5 + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF68217A + #FF68217A + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF999999 + #FF999999 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF0066CC + #FF0066CC + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF0066CC + #FF0066CC + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFA8B3C2 + #FFA8B3C2 + #FFFFFFFF + #FFFFFFFF + #FFA8B3C2 + #FFA8B3C2 + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF000000 + #FF000000 + #FFA8B3C2 + #FFA8B3C2 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF1B293E + #FF1B293E + #FF0066CC + #FF0066CC + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FFF0F0F0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FF2D2D30 + #FF2D2D30 + #FF7AC1FF + #FF7AC1FF + #FFC8C8C8 + #FFC8C8C8 + #00000000 + #00000000 + #FF3F3F46 + #FF3F3F46 + + + #FF2D2D30 + #FF2D2D30 + #FF656565 + #FF656565 + #FFFEFCC8 + #FFFEFCC8 + #FF1E1E1E + #FF1E1E1E + #001E1E1E + #001E1E1E + #00F1F1F1 + #00F1F1F1 + #FF0E639C + #FF0E639C + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FFCA5100 + #FFCA5100 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #FFFFFFFF + #FFFFFFFF + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #FF434346 + #FF434346 + #FF2D2D30 + #FF2D2D30 + #99999999 + #99999999 + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #FFFFFFFF + #FFFFFFFF + #7F000000 + #7F000000 + #FF333337 + #FF333337 + #002D2D30 + #002D2D30 + #FF1B1B1C + #FF1B1B1C + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF333337 + #FF333337 + #FF3F3F46 + #FF3F3F46 + #FF7D7D7D + #FF7D7D7D + #FF3399FF + #FF3399FF + #FFC6C6C6 + #FFC6C6C6 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF0097FB + #FF0097FB + #FF656565 + #FF656565 + #FF55AAFF + #FF55AAFF + #FFFFFFFF + #FFFFFFFF + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF2D2D30 + #FF2D2D30 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF333337 + #FF333337 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #FFFFFFFF + #FFFFFFFF + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #FFFEFCC8 + #FFFEFCC8 + #FFFEFCC8 + #FFFEFCC8 + #FF252526 + #FF252526 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF999999 + #FF999999 + #FF555558 + #FF555558 + #FF1C97EA + #FF1C97EA + #FF007ACC + #FF007ACC + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF1C1C1C + #FF1C1C1C + #FF3E3E42 + #FF3E3E42 + #FF686868 + #FF686868 + #FF686868 + #FF686868 + #FF3E3E42 + #FF3E3E42 + #FF686868 + #FF686868 + #FF9E9E9E + #FF9E9E9E + #FFEFEBEF + #FFEFEBEF + #FF9E9E9E + #FF9E9E9E + #FF9E9E9E + #FF9E9E9E + #FFEFEBEF + #FFEFEBEF + #FFEFEBEF + #FFEFEBEF + #FF333337 + #FF333337 + #FF333337 + #FF333337 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FFE5C365 + #FFE5C365 + #FFFFEFBB + #FFFFEFBB + #FFE5C365 + #FFE5C365 + #FFFEFCC8 + #FFFEFCC8 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF4169E1 + #FF4169E1 + #FF96A9DD + #FF96A9DD + #FFE122DF + #FFE122DF + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF434346 + #FF434346 + #FF1F1F22 + #FF1F1F22 + #FF1F1F22 + #FF1F1F22 + #FF999999 + #FF999999 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FFF30506 + #FFF30506 + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FFF30506 + #FFF30506 + #FFF30506 + #FFF30506 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF363639 + #FF363639 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF28282B + #FF28282B + #FF28282B + #FF28282B + #FFF1F1F1 + #FFF1F1F1 + #FFF30506 + #FFF30506 + #FFF1F1F1 + #FFF1F1F1 + #FF0097FB + #FF0097FB + #FF88CCFE + #FF88CCFE + #FFF1F1F1 + #FFF1F1F1 + #FF999999 + #FF999999 + #FF55AAFF + #FF55AAFF + #FFF1F1F1 + #FFF1F1F1 + #FF999999 + #FF999999 + #FF55AAFF + #FF55AAFF + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F3F + #FF3F3F3F + #FF464646 + #FF464646 + #FF999999 + #FF999999 + #FF0E639C + #FF0E639C + #FFFFFFFF + #FFFFFFFF + #FFCA5100 + #FFCA5100 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #4C000000 + #4C000000 + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF464646 + #FF464646 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF46464A + #FF46464A + #FF59A8DE + #FF59A8DE + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFD0D0D0 + #FFD0D0D0 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF656565 + #FF656565 + #FF3E3E40 + #FF3E3E40 + #FF656565 + #FF656565 + #FF333337 + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF424245 + #FF424245 + #FFF1F1F1 + #FFF1F1F1 + #FF4D4D50 + #FF4D4D50 + #FF717171 + #FF717171 + #FF252526 + #FF252526 + #FF3F3F46 + #FF3F3F46 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFFFFFFF + #FF393939 + #FF393939 + #FF393939 + #FF393939 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FF2D2D30 + #FF2D2D30 + #99999999 + #99999999 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF55AAFF + #FF55AAFF + #FF0097FB + #FF0097FB + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF0097FB + #FF0097FB + #FF3F3F46 + #FF3F3F46 + #FFD0D0D0 + #FFD0D0D0 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF705829 + #FF705829 + #FFB0A781 + #FFB0A781 + #FFA19667 + #FFA19667 + #FFA79432 + #FFA79432 + #FFD0D4B7 + #FFD0D4B7 + #FFBFC749 + #FFBFC749 + #FFCAB22D + #FFCAB22D + #FFFBF7C8 + #FFFBF7C8 + #FFE2E442 + #FFE2E442 + #FF5D8039 + #FF5D8039 + #FFB1C97B + #FFB1C97B + #FF9FB861 + #FF9FB861 + #FF8E5478 + #FF8E5478 + #FFE2B1CD + #FFE2B1CD + #FFCB98B6 + #FFCB98B6 + #FFAD1C2B + #FFAD1C2B + #FFFF9F99 + #FFFF9F99 + #FFFF7971 + #FFFF7971 + #FF779AB6 + #FF779AB6 + #FFC6D4DF + #FFC6D4DF + #FFB8CCD7 + #FFB8CCD7 + #FF427094 + #FF427094 + #FFA0B7C9 + #FFA0B7C9 + #FF89ABBD + #FF89ABBD + #FF5386BF + #FF5386BF + #FFB9D4EE + #FFB9D4EE + #FFA1C7E7 + #FFA1C7E7 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + + + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Dark/Theme.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Dark/Theme.xaml new file mode 100644 index 0000000000..0284cc36ae --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Dark/Theme.xaml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Dark/ThemedDialogColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Dark/ThemedDialogColors.xaml new file mode 100644 index 0000000000..ca9038bb89 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Dark/ThemedDialogColors.xaml @@ -0,0 +1,66 @@ + + #00000000 + #00000000 + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #FF999999 + #FF999999 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF0097FB + #FF0097FB + #FF656565 + #FF656565 + #FF55AAFF + #FF55AAFF + #FF0097FB + #FF0097FB + #FF656565 + #FF656565 + #FF3F3F40 + #FF3F3F40 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #00000000 + #00000000 + #00000000 + #00000000 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Dark/TreeViewColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Dark/TreeViewColors.xaml new file mode 100644 index 0000000000..305d7c8e70 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Dark/TreeViewColors.xaml @@ -0,0 +1,48 @@ + + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FFE51400 + #FFE51400 + #FFFFFFFF + #FFFFFFFF + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFFEFCC8 + #FFFEFCC8 + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFE51400 + #FFE51400 + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Dark/VsBrushes.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Dark/VsBrushes.xaml new file mode 100644 index 0000000000..a267c97c8d --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Dark/VsBrushes.xaml @@ -0,0 +1,826 @@ + + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF0097FB + #FFD0D0D0 + #FF000000 + #FF2D2D30 + #FF3F3F46 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF464646 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF0F2F9 + #FFD3DCEF + #FFCCCC66 + #FFFFFFCC + #FF000000 + #FFD2D2D2 + #FF808080 + #FF000000 + #FFFFFFFF + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFF7F0F0 + #FFEDDADC + #FFFFFFFF + #FF0054E3 + #FFDDD6EF + #FF266035 + #FFFFFFFF + #FF716F64 + #FFF3F7F0 + #FFE6F0DB + #FF808080 + #FF716F64 + #FFB0764F + #FF716F64 + #FF808080 + #FF716F64 + #FFD8D8D8 + #FF808080 + #FF716F64 + #FFD6ECEF + #FFFF0000 + #FFF8F4E9 + #FFF0E9D2 + #FF333337 + #FF434346 + #FF2D2D30 + #FF434346 + #FF434346 + #FFF1F1F1 + #FF3F3F46 + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF2D2D30 + #FF999999 + #FF46464A + #FF46464A + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3399FF + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF1B1B1C + #FF007ACC + #FF333337 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF2D2D30 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #72555555 + #72555555 + #72555555 + #72555555 + #FF007ACC + #FF2D2D30 + #FF3399FF + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FFF1F1F1 + #FF2D2D30 + #FF222222 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FFFEFCC8 + #FF555555 + #FF0097FB + #FF0097FB + #FF0097FB + #FF333337 + #FF424245 + #FF4D4D50 + #FF505051 + #FFF1F1F1 + #FF333337 + #FFF1F1F1 + #FF2C2C2F + #FF37373A + #FF3D3D3F + #FF7A7A7A + #FF333337 + #FF656565 + #FF252526 + #FF46464A + #FF3F3F46 + #FF656565 + #FFFFFFFF + #FFF2F4F8 + #FF000000 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFFFFFFF + #FF000000 + #FF1B1B1C + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF007ACC + #FF333337 + #FF434346 + #FF2D2D30 + #FF434346 + #FF434346 + #FF999999 + #FF3F3F46 + #FF434346 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF007ACC + #FF1B1B1C + #FF1B1B1C + #FF333337 + #72000000 + #FF333337 + #FF252526 + #FF0097FB + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFFF8C00 + #FFFF8C00 + #FF656565 + #FF656565 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF1C97EA + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFF1F1F1 + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF999999 + #FF2D2D30 + #FFF1F1F1 + #FF000000 + #FFDEE1E7 + #FF0066CC + #FF000000 + #FFFFFFFF + #FF0066CC + #FF000000 + #FFFFFFFF + #FFA8B3C2 + #FFFFFFFF + #FFA8B3C2 + #FF000000 + #FFDEE1E7 + #FF000000 + #FFA8B3C2 + #FF000000 + #FFFFFFFF + #FF000000 + #FFDEE1E7 + #FF1B293E + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FF000000 + #FF000000 + #FF3399FF + #FFFFFFFF + #FF3F3F46 + #FF2D2D30 + #FF656565 + #FFFEFCC8 + #FF1E1E1E + #FF333337 + #FF1B1B1C + #FFF1F1F1 + #FF252526 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3399FF + #FF3399FF + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF252526 + #FFF1F1F1 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FF333337 + #FF252526 + #FF252526 + #FF3E3E40 + #FF3E3E40 + #FF0097FB + #FF55AAFF + #FF0097FB + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FF252526 + #FFF1F1F1 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF2D2D30 + #FF252526 + #FF252526 + #FF2D2D30 + #FFFEFCC8 + #FFFEFCC8 + #FF252526 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF686868 + #FF686868 + #FF686868 + #FF9E9E9E + #FFEFEBEF + #FF333337 + #FF333337 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFE5C365 + #FFFFEFBB + #FFE5C365 + #FFFEFCC8 + #FF000000 + #FF000000 + #FF4169E1 + #FF96A9DD + #FFE122DF + #FF252526 + #FFF1F1F1 + #FF434346 + #FF1F1F22 + #FF1F1F22 + #FF999999 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FFF30506 + #FF0097FB + #FF55AAFF + #FFF30506 + #FF007ACC + #FFFFFFFF + #FF363639 + #FF252526 + #FF252526 + #FF28282B + #FF28282B + #FFF1F1F1 + #FFF30506 + #FFF1F1F1 + #FF0097FB + #FF88CCFE + #FFF1F1F1 + #FF999999 + #FF55AAFF + #FFF1F1F1 + #FF999999 + #FF55AAFF + #FFF1F1F1 + #FF3F3F3F + #FF464646 + #FF999999 + #FFFFFFFF + #FF000000 + #FF2D2D30 + #FF3F3F46 + #FF464646 + #FF2D2D30 + #FF3F3F46 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFD0D0D0 + #FF252526 + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF252526 + #FF3F3F46 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FF393939 + #FF393939 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF55AAFF + #FF252526 + #FF0097FB + #FFD0D0D0 + #FFF1F1F1 + #FF705829 + #FFB0A781 + #FFA19667 + #FFA79432 + #FFD0D4B7 + #FFBFC749 + #FFCAB22D + #FFFBF7C8 + #FFE2E442 + #FF5D8039 + #FFB1C97B + #FF9FB861 + #FF8E5478 + #FFE2B1CD + #FFCB98B6 + #FFAD1C2B + #FFFF9F99 + #FFFF7971 + #FF779AB6 + #FFC6D4DF + #FFB8CCD7 + #FF427094 + #FFA0B7C9 + #FF89ABBD + #FF5386BF + #FFB9D4EE + #FFA1C7E7 + #FF252526 + #FF2D2D30 + #FFF1F1F1 + #FFFFFFFF + #FF000000 + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Dark/VsColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Dark/VsColors.xaml new file mode 100644 index 0000000000..8fc8748b7d --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Dark/VsColors.xaml @@ -0,0 +1,505 @@ + + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF0097FB + #FFD0D0D0 + #FF000000 + #FF2D2D30 + #FF3F3F46 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF464646 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF0F2F9 + #FFD3DCEF + #FFCCCC66 + #FFFFFFCC + #FF000000 + #FFD2D2D2 + #FF808080 + #FF000000 + #FFFFFFFF + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFF7F0F0 + #FFEDDADC + #FFFFFFFF + #FF0054E3 + #FFDDD6EF + #FF266035 + #FFFFFFFF + #FF716F64 + #FFF3F7F0 + #FFE6F0DB + #FF808080 + #FF716F64 + #FFB0764F + #FF716F64 + #FF808080 + #FF716F64 + #FFD8D8D8 + #FF808080 + #FF716F64 + #FFD6ECEF + #FFFF0000 + #FFF8F4E9 + #FFF0E9D2 + #FF333337 + #FF434346 + #FF2D2D30 + #FF434346 + #FF434346 + #FFF1F1F1 + #FF3F3F46 + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF2D2D30 + #FF999999 + #FF46464A + #FF46464A + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3399FF + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF1B1B1C + #FF007ACC + #FF333337 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF2D2D30 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #72555555 + #72555555 + #72555555 + #72555555 + #FF007ACC + #FF2D2D30 + #FF3399FF + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FFF1F1F1 + #FF2D2D30 + #FF222222 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FFFEFCC8 + #FF555555 + #FF0097FB + #FF0097FB + #FF0097FB + #FF333337 + #FF424245 + #FF4D4D50 + #FF505051 + #FFF1F1F1 + #FF333337 + #FFF1F1F1 + #FF2C2C2F + #FF37373A + #FF3D3D3F + #FF7A7A7A + #FF333337 + #FF656565 + #FF252526 + #FF46464A + #FF3F3F46 + #FF656565 + #FFFFFFFF + #FFF2F4F8 + #FF000000 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFFFFFFF + #FF000000 + #FF1B1B1C + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF007ACC + #FF333337 + #FF434346 + #FF2D2D30 + #FF434346 + #FF434346 + #FF999999 + #FF3F3F46 + #FF434346 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF007ACC + #FF1B1B1C + #FF1B1B1C + #FF333337 + #72000000 + #FF333337 + #FF252526 + #FF0097FB + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFFF8C00 + #FFFF8C00 + #FF656565 + #FF656565 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF1C97EA + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFF1F1F1 + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF999999 + #FF2D2D30 + #FFF1F1F1 + #FF000000 + #FFDEE1E7 + #FF0066CC + #FF000000 + #FFFFFFFF + #FF0066CC + #FF000000 + #FFFFFFFF + #FFA8B3C2 + #FFFFFFFF + #FFA8B3C2 + #FF000000 + #FFDEE1E7 + #FF000000 + #FFA8B3C2 + #FF000000 + #FFFFFFFF + #FF000000 + #FFDEE1E7 + #FF1B293E + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FF000000 + #FF000000 + #FF3399FF + #FFFFFFFF + #FF3F3F46 + #FF2D2D30 + #FF656565 + #FFFEFCC8 + #FF1E1E1E + #FF333337 + #FF1B1B1C + #FFF1F1F1 + #FF252526 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3399FF + #FF3399FF + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF252526 + #FFF1F1F1 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FF333337 + #FF252526 + #FF252526 + #FF3E3E40 + #FF3E3E40 + #FF0097FB + #FF55AAFF + #FF0097FB + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FF252526 + #FFF1F1F1 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF2D2D30 + #FF252526 + #FF252526 + #FF2D2D30 + #FFFEFCC8 + #FFFEFCC8 + #FF252526 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF686868 + #FF686868 + #FF686868 + #FF9E9E9E + #FFEFEBEF + #FF333337 + #FF333337 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFE5C365 + #FFFFEFBB + #FFE5C365 + #FFFEFCC8 + #FF000000 + #FF000000 + #FF4169E1 + #FF96A9DD + #FFE122DF + #FF252526 + #FFF1F1F1 + #FF434346 + #FF1F1F22 + #FF1F1F22 + #FF999999 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FFF30506 + #FF0097FB + #FF55AAFF + #FFF30506 + #FF007ACC + #FFFFFFFF + #FF363639 + #FF252526 + #FF252526 + #FF28282B + #FF28282B + #FFF1F1F1 + #FFF30506 + #FFF1F1F1 + #FF0097FB + #FF88CCFE + #FFF1F1F1 + #FF999999 + #FF55AAFF + #FFF1F1F1 + #FF999999 + #FF55AAFF + #FFF1F1F1 + #FF3F3F3F + #FF464646 + #FF999999 + #FFFFFFFF + #FF000000 + #FF2D2D30 + #FF3F3F46 + #FF464646 + #FF2D2D30 + #FF3F3F46 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFD0D0D0 + #FF252526 + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF252526 + #FF3F3F46 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FF393939 + #FF393939 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF55AAFF + #FF252526 + #FF0097FB + #FFD0D0D0 + #FFF1F1F1 + #FF705829 + #FFB0A781 + #FFA19667 + #FFA79432 + #FFD0D4B7 + #FFBFC749 + #FFCAB22D + #FFFBF7C8 + #FFE2E442 + #FF5D8039 + #FFB1C97B + #FF9FB861 + #FF8E5478 + #FFE2B1CD + #FFCB98B6 + #FFAD1C2B + #FFFF9F99 + #FFFF7971 + #FF779AB6 + #FFC6D4DF + #FFB8CCD7 + #FF427094 + #FFA0B7C9 + #FF89ABBD + #FF5386BF + #FFB9D4EE + #FFA1C7E7 + #FF252526 + #FF2D2D30 + #FFF1F1F1 + #FFFFFFFF + #FF000000 + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/FontScalingLabelStyle.xaml b/src/GitHub.VisualStudio.TestApp/Themes/FontScalingLabelStyle.xaml new file mode 100644 index 0000000000..15a6a5a2a2 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/FontScalingLabelStyle.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/FontScalingTextBlockStyle.xaml b/src/GitHub.VisualStudio.TestApp/Themes/FontScalingTextBlockStyle.xaml new file mode 100644 index 0000000000..1992aa4407 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/FontScalingTextBlockStyle.xaml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/CommonControlsColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/CommonControlsColors.xaml new file mode 100644 index 0000000000..a5bf613bc4 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/CommonControlsColors.xaml @@ -0,0 +1,198 @@ + + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF00002F + #FF00002F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF00002F + #FF00002F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF37006E + #FF37006E + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FFFFFFFF + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/EnvironmentColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/EnvironmentColors.xaml new file mode 100644 index 0000000000..d460c2af17 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/EnvironmentColors.xaml @@ -0,0 +1,1588 @@ + + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FF37006E + #FF37006E + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFC0C0C0 + #FFC0C0C0 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFF00 + #FFFFFF00 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFFFF + #FFFFFFFF + #00C800C8 + #00C800C8 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF008000 + #FF008000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF000000 + #FF000000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF00529B + #FF00529B + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF008000 + #FF008000 + + + #FF00002F + #FF00002F + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #00C800C8 + #00C800C8 + #00C800C8 + #00C800C8 + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF00002F + #FF00002F + #00000000 + #00000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF00002F + #FF00002F + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #7F000000 + #7F000000 + #FF808080 + #FF808080 + #00C800C8 + #00C800C8 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF008000 + #FF008000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF008000 + #FF008000 + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FF3FF23F + #FF3FF23F + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FFC0C0C0 + #FFC0C0C0 + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF00002F + #FF00002F + #00000000 + #00000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFC0C0C0 + #FFC0C0C0 + #FF00002F + #FF00002F + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFC0C0C0 + #FFC0C0C0 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FFFF00FF + #FFFF00FF + #FFFF00FF + #FFFF00FF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FF00002F + #FF00002F + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + + + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/Theme.xaml b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/Theme.xaml new file mode 100644 index 0000000000..0284cc36ae --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/Theme.xaml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/ThemedDialogColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/ThemedDialogColors.xaml new file mode 100644 index 0000000000..fbae4aa2a5 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/ThemedDialogColors.xaml @@ -0,0 +1,66 @@ + + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF3FF23F + #FF3FF23F + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/TreeViewColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/TreeViewColors.xaml new file mode 100644 index 0000000000..c9ba4b4d84 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/TreeViewColors.xaml @@ -0,0 +1,48 @@ + + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/VsBrushes.xaml b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/VsBrushes.xaml new file mode 100644 index 0000000000..022e4ad948 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/VsBrushes.xaml @@ -0,0 +1,554 @@ + + #FF808080 + #FF808080 + #FFC0C0C0 + #FF000000 + #FF000000 + #FFFFFF00 + #FF37006E + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFC0C0C0 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF1AEBFF + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFFFF + #FF000000 + #FF808080 + #FF1AEBFF + #FF000000 + #FF808080 + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFFFF + #FF000000 + #FF808080 + #FF3FF23F + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF008000 + #FF008000 + #FF000000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF808080 + #FF000000 + #FF3FF23F + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF008000 + #FF00002F + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FF008000 + #FF000000 + #FF000000 + #FF008000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF000000 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFC0C0C0 + #FF00002F + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF000000 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FFFFFF00 + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFF00 + #FF1AEBFF + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF008000 + #FFFFFFFF + #FF000000 + #FFC0C0C0 + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFF00FF + #FFFF00FF + #FF37006E + #FF008000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/VsColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/VsColors.xaml new file mode 100644 index 0000000000..abd1f56ce5 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/HighContrast/VsColors.xaml @@ -0,0 +1,505 @@ + + #FF808080 + #FF808080 + #FFC0C0C0 + #FF000000 + #FF000000 + #FFFFFF00 + #FF37006E + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFC0C0C0 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF1AEBFF + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFFFF + #FF000000 + #FF808080 + #FF1AEBFF + #FF000000 + #FF808080 + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFFFF + #FF000000 + #FF808080 + #FF3FF23F + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF008000 + #FF008000 + #FF000000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF808080 + #FF000000 + #FF3FF23F + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF008000 + #FF00002F + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FF008000 + #FF000000 + #FF000000 + #FF008000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF000000 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFC0C0C0 + #FF00002F + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF000000 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FFFFFF00 + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFF00 + #FF1AEBFF + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF008000 + #FFFFFFFF + #FF000000 + #FFC0C0C0 + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFF00FF + #FFFF00FF + #FF37006E + #FF008000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Light/CommonControlsColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Light/CommonControlsColors.xaml new file mode 100644 index 0000000000..9f5b1146dc --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Light/CommonControlsColors.xaml @@ -0,0 +1,198 @@ + + #FFECECF0 + #FFECECF0 + #FF1E1E1E + #FF1E1E1E + #FFACACAC + #FFACACAC + #FF3399FF + #FF3399FF + #FFCCCEDB + #FFCCCEDB + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF007ACC + #FF007ACC + #FFECECF0 + #FFECECF0 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFA2A4A5 + #FFA2A4A5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFEFEFE + #FFFEFEFE + #FFF6F6F6 + #FFF6F6F6 + #FFF3F9FF + #FFF3F9FF + #FFF3F9FF + #FFF3F9FF + #FF007ACC + #FF007ACC + #FF717171 + #FF717171 + #FFC6C6C6 + #FFC6C6C6 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FF717171 + #FF717171 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF717171 + #FF717171 + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFF6F6F6 + #FFF6F6F6 + #19000000 + #19000000 + #FFCCCEDB + #FFCCCEDB + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #66007ACC + #66007ACC + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FF1E1E1E + #FF1E1E1E + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Light/EnvironmentColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Light/EnvironmentColors.xaml new file mode 100644 index 0000000000..295f6239fa --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Light/EnvironmentColors.xaml @@ -0,0 +1,1588 @@ + + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF525252 + #FF525252 + #FFFFFFFF + #FFFFFFFF + #FF525252 + #FF525252 + #80525252 + #80525252 + #FFFFFFFF + #FFFFFFFF + #80525252 + #80525252 + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF0E70C0 + #FF0E70C0 + #FF444444 + #FF444444 + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEBD + #FFCCCEBD + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FFD8D8E0 + #FFD8D8E0 + #FFCCCEBD + #FFCCCEBD + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFF0F2F9 + #FFF0F2F9 + #FFD3DCEF + #FFD3DCEF + #FFCCCC66 + #FFCCCC66 + #FFFFFFCC + #FFFFFFCC + #FF000000 + #FF000000 + #FFD2D2D2 + #FFD2D2D2 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF00008B + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFF7F0F0 + #FFF7F0F0 + #FFEDDADC + #FFEDDADC + #FFFFFFFF + #FFFFFFFF + #FF0054E3 + #FF0054E3 + #FFDDD6EF + #FFDDD6EF + #FF266035 + #FF266035 + #FFFFFFFF + #FFFFFFFF + #FF716F64 + #FF716F64 + #FFF3F7F0 + #FFF3F7F0 + #FFE6F0DB + #FFE6F0DB + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFB0764F + #FFB0764F + #FF716F64 + #FF716F64 + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFD8D8D8 + #FFD8D8D8 + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFD6ECEF + #FFD6ECEF + #FFFF0000 + #FFFF0000 + #FFF8F4E9 + #FFF8F4E9 + #FFF0E9D2 + #FFF0E9D2 + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFA2A4A5 + #FFA2A4A5 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF717171 + #FF717171 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FFA2A4A5 + #FFA2A4A5 + #FF1E1E1E + #FF1E1E1E + #FF999999 + #FF999999 + #FF999999 + #FF999999 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF3399FF + #FF3399FF + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FF0E70C0 + #FF0E70C0 + #FF007ACC + #FF007ACC + #FFF6F6F6 + #FFF6F6F6 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFC9DEF5 + #FFC9DEF5 + #FF0E70C0 + #FF0E70C0 + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFE0E3E6 + #FFE0E3E6 + #FF007ACC + #FF007ACC + #FF717171 + #FF717171 + #FFE0E3E6 + #FFE0E3E6 + #FF717171 + #FF717171 + #FFFFFFFF + #FFFFFFFF + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF717171 + #FF717171 + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF3399FF + #FF3399FF + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FFFDFBAC + #FFFDFBAC + #FF717171 + #FF717171 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FFCCCEDB + #FFCCCEDB + #00CCCEDB + #00CCCEDB + #FFE7E8EC + #FFE7E8EC + #FFCCCEDB + #FFCCCEDB + #FFEDEEF0 + #FFEDEEF0 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFD6D8DC + #FFD6D8DC + #FFCCCEDB + #FFCCCEDB + #FFEDEEF0 + #FFEDEEF0 + #FFA2A4A5 + #FFA2A4A5 + #FFCCCEDB + #FFCCCEDB + #FFA2A4A5 + #FFA2A4A5 + #FFF5F5F5 + #FFF5F5F5 + #FF999999 + #FF999999 + #FFCCCEDB + #FFCCCEDB + #FFA2A4A5 + #FFA2A4A5 + #FFFFFFFF + #FFFFFFFF + #FF0E70C0 + #FF0E70C0 + #FF007ACC + #FF007ACC + #FFF2F4F8 + #FFF2F4F8 + #FF000000 + #FF000000 + #FF4A6184 + #FF4A6184 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFBCC7D8 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFF0F0F0 + #FFF0F0F0 + #FF000000 + #FF000000 + #FFE7E8EC + #FFE7E8EC + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFA2A4A5 + #FFA2A4A5 + #FF717171 + #FF717171 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #72000000 + #72000000 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FF0E70C0 + #FF0E70C0 + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFFFA300 + #FFFFA300 + #FFFFA300 + #FFFFA300 + #FFA2A4A5 + #FFA2A4A5 + #FFA2A4A5 + #FFA2A4A5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FFB7B9C5 + #FFB7B9C5 + #FFB7B9C5 + #FFB7B9C5 + #FF2D2D2D + #FF2D2D2D + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFFFFFFF + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFFFFFFF + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FF717171 + #FF717171 + #FF442359 + #FF442359 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FF442359 + #FF442359 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FFB7B9C5 + #FFB7B9C5 + #FFB7B9C5 + #FFB7B9C5 + #FF2D2D2D + #FF2D2D2D + #FFE1D3E4 + #FFE1D3E4 + #FFB064AB + #FFB064AB + #FFB064AB + #FFB064AB + #FFFFFFFF + #FFFFFFFF + #FF9B4F96 + #FF9B4F96 + #FF9B4F96 + #FF9B4F96 + #FFFFFFFF + #FFFFFFFF + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FF717171 + #FF717171 + #FF717171 + #FF717171 + #FFE1D3E4 + #FFE1D3E4 + #FF6D6D70 + #FF6D6D70 + #FFD0E6F5 + #FFD0E6F5 + #FF6D6D70 + #FF6D6D70 + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF1C97EA + #FF1C97EA + #FFD0E6F5 + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FF68217A + #FF68217A + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF717171 + #FF717171 + #FFEFEFE2 + #FFEFEFE2 + #FF1E1E1E + #FF1E1E1E + #FFF0F0F0 + #FFF0F0F0 + #FFDEE1E7 + #FFDEE1E7 + #FF0066CC + #FF0066CC + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF0066CC + #FF0066CC + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFA8B3C2 + #FFA8B3C2 + #FFFFFFFF + #FFFFFFFF + #FFA8B3C2 + #FFA8B3C2 + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF000000 + #FF000000 + #FFA8B3C2 + #FFA8B3C2 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF1B293E + #FF1B293E + #FF0066CC + #FF0066CC + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FFF0F0F0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FF00529B + #FF00529B + #FF424242 + #FF424242 + #00000000 + #00000000 + #FFCCCEDB + #FFCCCEDB + + + #FFEEEEF2 + #FFEEEEF2 + #FFA2A4A5 + #FFA2A4A5 + #FFFDFBAC + #FFFDFBAC + #FF1E1E1E + #FF1E1E1E + #00F5F5F5 + #00F5F5F5 + #001E1E1E + #001E1E1E + #FF9B9FB9 + #FF9B9FB9 + #FFEEEEF2 + #FFEEEEF2 + #FF525252 + #FF525252 + #FF9B9FB9 + #FF9B9FB9 + #FF9B9FB9 + #FF9B9FB9 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF9B9FB9 + #FF9B9FB9 + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #FF007ACC + #FF007ACC + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #FF007ACC + #FF007ACC + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #99525252 + #99525252 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #FFFFFFFF + #FFFFFFFF + #7F000000 + #7F000000 + #FFCCCEDB + #FFCCCEDB + #00EEEEF2 + #00EEEEF2 + #FFF6F6F6 + #FFF6F6F6 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FF1E1E1E + #FF1E1E1E + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFFEFEFE + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FFCCCED8 + #FFCCCED8 + #FF636363 + #FF636363 + #FF3399FF + #FF3399FF + #FFD0D0D0 + #FFD0D0D0 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FF0E70C0 + #FF0E70C0 + #FFA2A4A5 + #FFA2A4A5 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FFEEEEF2 + #FFEEEEF2 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFFCFCFC + #FFFCFCFC + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #FF007ACC + #FF007ACC + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #FF007ACC + #FF007ACC + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #FFFDFBAC + #FFFDFBAC + #FFFDFBAC + #FFFDFBAC + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF868999 + #FF868999 + #FFF5F5F5 + #FFF5F5F5 + #FF1C97EA + #FF1C97EA + #FF007ACC + #FF007ACC + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFFFFFFF + #FFFFFFFF + #FFF5F5F5 + #FFF5F5F5 + #FFC2C3C9 + #FFC2C3C9 + #FFC2C3C9 + #FFC2C3C9 + #FFF5F5F5 + #FFF5F5F5 + #FFC2C3C9 + #FFC2C3C9 + #FF686868 + #FF686868 + #FF5B5B5B + #FF5B5B5B + #FF686868 + #FF686868 + #FF686868 + #FF686868 + #FF5B5B5B + #FF5B5B5B + #FF5B5B5B + #FF5B5B5B + #FFFCFCFC + #FFFCFCFC + #FFFCFCFC + #FFFCFCFC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFE5C365 + #FFE5C365 + #FFFFEFBB + #FFFFEFBB + #FFE5C365 + #FFE5C365 + #FFFDFBAC + #FFFDFBAC + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF4169E1 + #FF4169E1 + #FF96A9DD + #FF96A9DD + #FFE122DF + #FFE122DF + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FF999999 + #FF999999 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFF30506 + #FFF30506 + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FFF30506 + #FFF30506 + #FFF30506 + #FFF30506 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF363639 + #FF363639 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FFF30506 + #FFF30506 + #FF555555 + #FF555555 + #FF007ACC + #FF007ACC + #FF77AAFF + #FF77AAFF + #FF1E1E1E + #FF1E1E1E + #FF999999 + #FF999999 + #FF007ACC + #FF007ACC + #FF555555 + #FF555555 + #FF999999 + #FF999999 + #FF007ACC + #FF007ACC + #FF000000 + #FF000000 + #FF3F3F3F + #FF3F3F3F + #FF464646 + #FF464646 + #FF999999 + #FF999999 + #FF0E639C + #FF0E639C + #FFFFFFFF + #FFFFFFFF + #FFCA5100 + #FFCA5100 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #4C000000 + #4C000000 + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFF0F0F0 + #FFF0F0F0 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFD8D8E0 + #FFD8D8E0 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEBD + #FFCCCEBD + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF999999 + #FF999999 + #FF59A8DE + #FF59A8DE + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF444444 + #FF444444 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFA2A4A5 + #FFA2A4A5 + #FFC9DEF5 + #FFC9DEF5 + #FFA2A4A5 + #FFA2A4A5 + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFF6F6F6 + #FFF6F6F6 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FFF5F5F5 + #FFF5F5F5 + #FFCCCEDB + #FFCCCEDB + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFFFFFFF + #FFF7F7F9 + #FFF7F7F9 + #FFF7F7F9 + #FFF7F7F9 + #FF717171 + #FF717171 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF525252 + #FF525252 + #FFEEEEF2 + #FFEEEEF2 + #99525252 + #99525252 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF0E70C0 + #FF0E70C0 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF0E70C0 + #FF0E70C0 + #FFCCCEDB + #FFCCCEDB + #FF444444 + #FF444444 + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF705829 + #FF705829 + #FFB0A781 + #FFB0A781 + #FFA19667 + #FFA19667 + #FFA79432 + #FFA79432 + #FFD0D4B7 + #FFD0D4B7 + #FFBFC749 + #FFBFC749 + #FFCAB22D + #FFCAB22D + #FFFBF7C8 + #FFFBF7C8 + #FFE2E442 + #FFE2E442 + #FF5D8039 + #FF5D8039 + #FFB1C97B + #FFB1C97B + #FF9FB861 + #FF9FB861 + #FF8E5478 + #FF8E5478 + #FFE2B1CD + #FFE2B1CD + #FFCB98B6 + #FFCB98B6 + #FFAD1C2B + #FFAD1C2B + #FFFF9F99 + #FFFF9F99 + #FFFF7971 + #FFFF7971 + #FF779AB6 + #FF779AB6 + #FFC6D4DF + #FFC6D4DF + #FFB8CCD7 + #FFB8CCD7 + #FF427094 + #FF427094 + #FFA0B7C9 + #FFA0B7C9 + #FF89ABBD + #FF89ABBD + #FF5386BF + #FF5386BF + #FFB9D4EE + #FFB9D4EE + #FFA1C7E7 + #FFA1C7E7 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + + + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Light/Theme.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Light/Theme.xaml new file mode 100644 index 0000000000..0284cc36ae --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Light/Theme.xaml @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Light/ThemedDialogColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Light/ThemedDialogColors.xaml new file mode 100644 index 0000000000..af98768985 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Light/ThemedDialogColors.xaml @@ -0,0 +1,66 @@ + + #00000000 + #00000000 + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #FF717171 + #FF717171 + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FF0E70C0 + #FF0E70C0 + #FFA2A4A5 + #FFA2A4A5 + #FF007ACC + #FF007ACC + #FF0E70C0 + #FF0E70C0 + #FFA2A4A5 + #FFA2A4A5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #00000000 + #00000000 + #00000000 + #00000000 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFFBFBFB + #FFFBFBFB + #FF1E1E1E + #FF1E1E1E + #FFEFEFF2 + #FFEFEFF2 + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Light/TreeViewColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Light/TreeViewColors.xaml new file mode 100644 index 0000000000..2eed34a26b --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Light/TreeViewColors.xaml @@ -0,0 +1,48 @@ + + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFE51400 + #FFE51400 + #FF000000 + #FF000000 + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFD0F7FF + #FFD0F7FF + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFE51400 + #FFE51400 + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Light/VsBrushes.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Light/VsBrushes.xaml new file mode 100644 index 0000000000..fc402c491b --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Light/VsBrushes.xaml @@ -0,0 +1,554 @@ + + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF0E70C0 + #FF444444 + #FFFFFFFF + #FFEEEEF2 + #FFCCCEBD + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFD8D8E0 + #FFCCCEBD + #FF1E1E1E + #FF1E1E1E + #FFF0F2F9 + #FFD3DCEF + #FFCCCC66 + #FFFFFFCC + #FF000000 + #FFD2D2D2 + #FF808080 + #FF000000 + #FFFFFFFF + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFF7F0F0 + #FFEDDADC + #FFFFFFFF + #FF0054E3 + #FFDDD6EF + #FF266035 + #FFFFFFFF + #FF716F64 + #FFF3F7F0 + #FFE6F0DB + #FF808080 + #FF716F64 + #FFB0764F + #FF716F64 + #FF808080 + #FF716F64 + #FFD8D8D8 + #FF808080 + #FF716F64 + #FFD6ECEF + #FFFF0000 + #FFF8F4E9 + #FFF0E9D2 + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FFFFFFFF + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFEEEEF2 + #FF717171 + #FF999999 + #FF999999 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF3399FF + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFF6F6F6 + #FF007ACC + #FFE0E3E6 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFEEEEF2 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FFEEEEF2 + #FF3399FF + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FF1E1E1E + #FFEEEEF2 + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FFFDFBAC + #FF717171 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FFCCCEDB + #FFE7E8EC + #FFCCCEDB + #FFEDEEF0 + #FF1E1E1E + #FFCCCEDB + #FF1E1E1E + #FFD6D8DC + #FFCCCEDB + #FFEDEEF0 + #FFA2A4A5 + #FFCCCEDB + #FFA2A4A5 + #FFF5F5F5 + #FF999999 + #FFCCCEDB + #FFA2A4A5 + #FFFFFFFF + #FFF2F4F8 + #FF000000 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFFFFFFF + #FF000000 + #FFE7E8EC + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FF007ACC + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FFFFFFFF + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #72000000 + #FFCCCEDB + #FFF5F5F5 + #FF0E70C0 + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFFFA300 + #FFFFA300 + #FFA2A4A5 + #FFA2A4A5 + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF1C97EA + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FF1E1E1E + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF717171 + #FFEFEFE2 + #FF1E1E1E + #FFF0F0F0 + #FFDEE1E7 + #FF0066CC + #FF000000 + #FFFFFFFF + #FF0066CC + #FF000000 + #FFFFFFFF + #FFA8B3C2 + #FFFFFFFF + #FFA8B3C2 + #FF000000 + #FFDEE1E7 + #FF000000 + #FFA8B3C2 + #FF000000 + #FFFFFFFF + #FF000000 + #FFDEE1E7 + #FF1B293E + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FF000000 + #FF000000 + #FF3399FF + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFA2A4A5 + #FFFDFBAC + #FF1E1E1E + #FFCCCEDB + #FFF6F6F6 + #FF1E1E1E + #FFF5F5F5 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF3399FF + #FF3399FF + #FFFEFEFE + #FFFEFEFE + #FF1E1E1E + #FFFEFEFE + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFF5F5F5 + #FF1E1E1E + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFFEFEFE + #FFFEFEFE + #FF0E70C0 + #FF007ACC + #FF0E70C0 + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FFF5F5F5 + #FF1E1E1E + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FFEEEEF2 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFFDFBAC + #FFFDFBAC + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFC2C3C9 + #FFC2C3C9 + #FFC2C3C9 + #FF686868 + #FF5B5B5B + #FFFCFCFC + #FFFCFCFC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFE5C365 + #FFFFEFBB + #FFE5C365 + #FFFDFBAC + #FF000000 + #FF000000 + #FF4169E1 + #FF96A9DD + #FFE122DF + #FFF5F5F5 + #FF1E1E1E + #FF999999 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFF30506 + #FF0097FB + #FF55AAFF + #FFF30506 + #FF007ACC + #FFFFFFFF + #FF363639 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF30506 + #FF555555 + #FF007ACC + #FF77AAFF + #FF1E1E1E + #FF999999 + #FF007ACC + #FF555555 + #FF999999 + #FF007ACC + #FF000000 + #FF3F3F3F + #FF464646 + #FF999999 + #FFFFFFFF + #FFF0F0F0 + #FFEEEEF2 + #FFCCCEDB + #FFD8D8E0 + #FFEEEEF2 + #FFCCCEBD + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF444444 + #FFF5F5F5 + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFF5F5F5 + #FFCCCEDB + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFF7F7F9 + #FFF7F7F9 + #FF717171 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FFF5F5F5 + #FF0E70C0 + #FF444444 + #FF1E1E1E + #FF705829 + #FFB0A781 + #FFA19667 + #FFA79432 + #FFD0D4B7 + #FFBFC749 + #FFCAB22D + #FFFBF7C8 + #FFE2E442 + #FF5D8039 + #FFB1C97B + #FF9FB861 + #FF8E5478 + #FFE2B1CD + #FFCB98B6 + #FFAD1C2B + #FFFF9F99 + #FFFF7971 + #FF779AB6 + #FFC6D4DF + #FFB8CCD7 + #FF427094 + #FFA0B7C9 + #FF89ABBD + #FF5386BF + #FFB9D4EE + #FFA1C7E7 + #FFF5F5F5 + #FFEEEEF2 + #FF1E1E1E + #FFFFFFFF + #FF000000 + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/Light/VsColors.xaml b/src/GitHub.VisualStudio.TestApp/Themes/Light/VsColors.xaml new file mode 100644 index 0000000000..6aa201c194 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/Light/VsColors.xaml @@ -0,0 +1,505 @@ + + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF0E70C0 + #FF444444 + #FFFFFFFF + #FFEEEEF2 + #FFCCCEBD + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFD8D8E0 + #FFCCCEBD + #FF1E1E1E + #FF1E1E1E + #FFF0F2F9 + #FFD3DCEF + #FFCCCC66 + #FFFFFFCC + #FF000000 + #FFD2D2D2 + #FF808080 + #FF000000 + #FFFFFFFF + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFF7F0F0 + #FFEDDADC + #FFFFFFFF + #FF0054E3 + #FFDDD6EF + #FF266035 + #FFFFFFFF + #FF716F64 + #FFF3F7F0 + #FFE6F0DB + #FF808080 + #FF716F64 + #FFB0764F + #FF716F64 + #FF808080 + #FF716F64 + #FFD8D8D8 + #FF808080 + #FF716F64 + #FFD6ECEF + #FFFF0000 + #FFF8F4E9 + #FFF0E9D2 + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FFFFFFFF + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFEEEEF2 + #FF717171 + #FF999999 + #FF999999 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF3399FF + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFF6F6F6 + #FF007ACC + #FFE0E3E6 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFEEEEF2 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FFEEEEF2 + #FF3399FF + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FF1E1E1E + #FFEEEEF2 + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FFFDFBAC + #FF717171 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FFCCCEDB + #FFE7E8EC + #FFCCCEDB + #FFEDEEF0 + #FF1E1E1E + #FFCCCEDB + #FF1E1E1E + #FFD6D8DC + #FFCCCEDB + #FFEDEEF0 + #FFA2A4A5 + #FFCCCEDB + #FFA2A4A5 + #FFF5F5F5 + #FF999999 + #FFCCCEDB + #FFA2A4A5 + #FFFFFFFF + #FFF2F4F8 + #FF000000 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFFFFFFF + #FF000000 + #FFE7E8EC + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FF007ACC + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FFFFFFFF + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #72000000 + #FFCCCEDB + #FFF5F5F5 + #FF0E70C0 + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFFFA300 + #FFFFA300 + #FFA2A4A5 + #FFA2A4A5 + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF1C97EA + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FF1E1E1E + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF717171 + #FFEFEFE2 + #FF1E1E1E + #FFF0F0F0 + #FFDEE1E7 + #FF0066CC + #FF000000 + #FFFFFFFF + #FF0066CC + #FF000000 + #FFFFFFFF + #FFA8B3C2 + #FFFFFFFF + #FFA8B3C2 + #FF000000 + #FFDEE1E7 + #FF000000 + #FFA8B3C2 + #FF000000 + #FFFFFFFF + #FF000000 + #FFDEE1E7 + #FF1B293E + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FF000000 + #FF000000 + #FF3399FF + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFA2A4A5 + #FFFDFBAC + #FF1E1E1E + #FFCCCEDB + #FFF6F6F6 + #FF1E1E1E + #FFF5F5F5 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF3399FF + #FF3399FF + #FFFEFEFE + #FFFEFEFE + #FF1E1E1E + #FFFEFEFE + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFF5F5F5 + #FF1E1E1E + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFFEFEFE + #FFFEFEFE + #FF0E70C0 + #FF007ACC + #FF0E70C0 + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FFF5F5F5 + #FF1E1E1E + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FFEEEEF2 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFFDFBAC + #FFFDFBAC + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFC2C3C9 + #FFC2C3C9 + #FFC2C3C9 + #FF686868 + #FF5B5B5B + #FFFCFCFC + #FFFCFCFC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFE5C365 + #FFFFEFBB + #FFE5C365 + #FFFDFBAC + #FF000000 + #FF000000 + #FF4169E1 + #FF96A9DD + #FFE122DF + #FFF5F5F5 + #FF1E1E1E + #FF999999 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFF30506 + #FF0097FB + #FF55AAFF + #FFF30506 + #FF007ACC + #FFFFFFFF + #FF363639 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF30506 + #FF555555 + #FF007ACC + #FF77AAFF + #FF1E1E1E + #FF999999 + #FF007ACC + #FF555555 + #FF999999 + #FF007ACC + #FF000000 + #FF3F3F3F + #FF464646 + #FF999999 + #FFFFFFFF + #FFF0F0F0 + #FFEEEEF2 + #FFCCCEDB + #FFD8D8E0 + #FFEEEEF2 + #FFCCCEBD + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF444444 + #FFF5F5F5 + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFF5F5F5 + #FFCCCEDB + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFF7F7F9 + #FFF7F7F9 + #FF717171 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FFF5F5F5 + #FF0E70C0 + #FF444444 + #FF1E1E1E + #FF705829 + #FFB0A781 + #FFA19667 + #FFA79432 + #FFD0D4B7 + #FFBFC749 + #FFCAB22D + #FFFBF7C8 + #FFE2E442 + #FF5D8039 + #FFB1C97B + #FF9FB861 + #FF8E5478 + #FFE2B1CD + #FFCB98B6 + #FFAD1C2B + #FFFF9F99 + #FFFF7971 + #FF779AB6 + #FFC6D4DF + #FFB8CCD7 + #FF427094 + #FFA0B7C9 + #FF89ABBD + #FF5386BF + #FFB9D4EE + #FFA1C7E7 + #FFF5F5F5 + #FFEEEEF2 + #FF1E1E1E + #FFFFFFFF + #FF000000 + diff --git a/src/GitHub.VisualStudio.TestApp/Themes/ThemedDialogDefaultStyles.xaml b/src/GitHub.VisualStudio.TestApp/Themes/ThemedDialogDefaultStyles.xaml new file mode 100644 index 0000000000..fe50c2cb4e --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Themes/ThemedDialogDefaultStyles.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.TestApp/Views/ExternalGitHubDialogWindow.xaml b/src/GitHub.VisualStudio.TestApp/Views/ExternalGitHubDialogWindow.xaml new file mode 100644 index 0000000000..1c6c8dbd98 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Views/ExternalGitHubDialogWindow.xaml @@ -0,0 +1,31 @@ + + + + + + + + diff --git a/src/GitHub.VisualStudio.TestApp/Views/ExternalGitHubDialogWindow.xaml.cs b/src/GitHub.VisualStudio.TestApp/Views/ExternalGitHubDialogWindow.xaml.cs new file mode 100644 index 0000000000..a3d0cb0fa2 --- /dev/null +++ b/src/GitHub.VisualStudio.TestApp/Views/ExternalGitHubDialogWindow.xaml.cs @@ -0,0 +1,19 @@ +using System; +using System.Windows; +using GitHub.ViewModels.Dialog; + +namespace GitHub.VisualStudio.Views.Dialog +{ + /// + /// The main window for GitHub for Visual Studio's dialog. + /// + public partial class ExternalGitHubDialogWindow : Window + { + public ExternalGitHubDialogWindow(IGitHubDialogWindowViewModel viewModel) + { + DataContext = viewModel; + viewModel.Done.Subscribe(_ => Close()); + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio.UI.16/GitHub.VisualStudio.UI.16.csproj b/src/GitHub.VisualStudio.UI.16/GitHub.VisualStudio.UI.16.csproj new file mode 100644 index 0000000000..6a39634b7c --- /dev/null +++ b/src/GitHub.VisualStudio.UI.16/GitHub.VisualStudio.UI.16.csproj @@ -0,0 +1,71 @@ + + + + + Debug + AnyCPU + {00423E7A-0838-4BE1-9263-181006DFF96B} + Library + GitHub.VisualStudio.UI._16 + GitHub.VisualStudio.UI.16 + v4.7.2 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + 4.0 + + + + + + + + MSBuild:Compile + Designer + + + + + Properties\SolutionInfo.cs + + + Code + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI.16/Properties/AssemblyInfo.cs b/src/GitHub.VisualStudio.UI.16/Properties/AssemblyInfo.cs new file mode 100644 index 0000000000..a73cb70d12 --- /dev/null +++ b/src/GitHub.VisualStudio.UI.16/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ +using System.Reflection; + +[assembly: AssemblyTitle("GitHub.VisualStudio.UI.16")] +[assembly: AssemblyDescription("GitHub Extension UI for Visual Studio 2019")] diff --git a/src/GitHub.VisualStudio.UI.16/Resources/icons/mark_github.xaml b/src/GitHub.VisualStudio.UI.16/Resources/icons/mark_github.xaml new file mode 100644 index 0000000000..c364f48ea7 --- /dev/null +++ b/src/GitHub.VisualStudio.UI.16/Resources/icons/mark_github.xaml @@ -0,0 +1,19 @@ + + + + + #424242 + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs b/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs index acab84eab8..b143dd9018 100644 --- a/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs +++ b/src/GitHub.VisualStudio.UI/Base/TeamExplorerItemBase.cs @@ -87,9 +87,9 @@ void TeamExplorerContext_PropertyChanged(object sender, PropertyChangedEventArgs void UpdateRepoOnMainThread(LocalRepositoryModel repo) { - holder.JoinableTaskFactory.RunAsync(async () => + holder.JoinableTaskContext.Factory.RunAsync(async () => { - await holder.JoinableTaskFactory.SwitchToMainThreadAsync(); + await holder.JoinableTaskContext.Factory.SwitchToMainThreadAsync(); UpdateRepo(repo); }).Task.Forget(); } @@ -123,12 +123,8 @@ protected virtual void RepoChanged() } } - protected async Task GetRepositoryOrigin() + protected async Task GetRepositoryOrigin(UriString uri) { - if (ActiveRepo == null) - return RepositoryOrigin.NonGitRepository; - - var uri = ActiveRepoUri; if (uri == null) return RepositoryOrigin.Other; @@ -154,15 +150,15 @@ protected async Task GetRepositoryOrigin() return RepositoryOrigin.Other; } - protected async Task IsAGitHubRepo() + protected async Task IsAGitHubRepo(UriString uri) { - var origin = await GetRepositoryOrigin(); + var origin = await GetRepositoryOrigin(uri); return origin == RepositoryOrigin.DotCom || origin == RepositoryOrigin.Enterprise; } - protected async Task IsAGitHubDotComRepo() + protected async Task IsAGitHubDotComRepo(UriString uri) { - var origin = await GetRepositoryOrigin(); + var origin = await GetRepositoryOrigin(uri); return origin == RepositoryOrigin.DotCom; } diff --git a/src/GitHub.VisualStudio.UI/Colors.cs b/src/GitHub.VisualStudio.UI/Colors.cs index f9966c2d5d..a28bf75a7e 100644 --- a/src/GitHub.VisualStudio.UI/Colors.cs +++ b/src/GitHub.VisualStudio.UI/Colors.cs @@ -1,21 +1,22 @@ using Microsoft.VisualStudio.PlatformUI; using System; +using System.Windows; using System.Windows.Media; namespace GitHub.VisualStudio.Helpers { public static class Colors { - public static Color RedNavigationItem = Color.FromRgb(0xF0, 0x50, 0x33); - public static Color BlueNavigationItem = Color.FromRgb(0x00, 0x79, 0xCE); - public static Color LightBlueNavigationItem = Color.FromRgb(0x00, 0x9E, 0xCE); - public static Color DarkPurpleNavigationItem = Color.FromRgb(0x68, 0x21, 0x7A); - public static Color GrayNavigationItem = Color.FromRgb(0x73, 0x82, 0x8C); - public static Color YellowNavigationItem = Color.FromRgb(0xF9, 0xC9, 0x00); - public static Color PurpleNavigationItem = Color.FromRgb(0xAE, 0x3C, 0xBA); + public static readonly Color RedNavigationItem = Color.FromRgb(0xF0, 0x50, 0x33); + public static readonly Color BlueNavigationItem = Color.FromRgb(0x00, 0x79, 0xCE); + public static readonly Color LightBlueNavigationItem = Color.FromRgb(0x00, 0x9E, 0xCE); + public static readonly Color DarkPurpleNavigationItem = Color.FromRgb(0x68, 0x21, 0x7A); + public static readonly Color GrayNavigationItem = Color.FromRgb(0x73, 0x82, 0x8C); + public static readonly Color YellowNavigationItem = Color.FromRgb(0xF9, 0xC9, 0x00); + public static readonly Color PurpleNavigationItem = Color.FromRgb(0xAE, 0x3C, 0xBA); - public static Color LightThemeNavigationItem = Color.FromRgb(66, 66, 66); - public static Color DarkThemeNavigationItem = Color.FromRgb(200, 200, 200); + public static readonly Color LightThemeNavigationItem = Color.FromRgb(66, 66, 66); + public static readonly Color DarkThemeNavigationItem = Color.FromRgb(200, 200, 200); public static int ToInt32(this Color color) { @@ -28,31 +29,28 @@ public static Color ToColor(this System.Drawing.Color color) } - static Color AccentMediumDarkTheme = Color.FromRgb(45, 45, 48); - static Color AccentMediumLightTheme = Color.FromRgb(238, 238, 242); - static Color AccentMediumBlueTheme = Color.FromRgb(255, 236, 181); + static readonly Color AccentMediumDarkTheme = Color.FromRgb(45, 45, 48); + static readonly Color AccentMediumLightTheme = Color.FromRgb(238, 238, 242); + static readonly Color AccentMediumBlueTheme = Color.FromRgb(255, 236, 181); public static string DetectTheme() { - try + if (Application.Current?.TryFindResource(EnvironmentColors.AccentMediumColorKey) is Color cc) { - var color = VSColorTheme.GetThemedColor(EnvironmentColors.AccentMediumColorKey); - var cc = color.ToColor(); if (cc == AccentMediumBlueTheme) return "Blue"; if (cc == AccentMediumLightTheme) return "Light"; if (cc == AccentMediumDarkTheme) return "Dark"; + var color = System.Drawing.Color.FromArgb(cc.A, cc.R, cc.R, cc.B); var brightness = color.GetBrightness(); var dark = brightness < 0.5f; return dark ? "Dark" : "Light"; } - // this throws in design time and when running outside of VS - catch (ArgumentNullException) - { - return "Dark"; - } + + // When Visual Studio resources aren't active + return "Dark"; } } } diff --git a/src/GitHub.VisualStudio.UI/Constants.cs b/src/GitHub.VisualStudio.UI/Constants.cs index 0378a5e916..e72248209b 100644 --- a/src/GitHub.VisualStudio.UI/Constants.cs +++ b/src/GitHub.VisualStudio.UI/Constants.cs @@ -1,4 +1,6 @@ -namespace GitHub.VisualStudio.UI +#pragma warning disable CA1707 // Identifiers should not contain underscores + +namespace GitHub.VisualStudio.UI { public static class Constants { diff --git a/src/GitHub.VisualStudio.UI/GitHub.VisualStudio.UI.csproj b/src/GitHub.VisualStudio.UI/GitHub.VisualStudio.UI.csproj index e99c2630a9..03a99b06bd 100644 --- a/src/GitHub.VisualStudio.UI/GitHub.VisualStudio.UI.csproj +++ b/src/GitHub.VisualStudio.UI/GitHub.VisualStudio.UI.csproj @@ -9,10 +9,15 @@ + + ..\..\lib\14.0\Microsoft.VisualStudio.Shell.ViewManager.dll + + + @@ -22,11 +27,17 @@ - - - - + + + + + MSBuild:Compile + Designer + true + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/GlobalSuppressions.cs b/src/GitHub.VisualStudio.UI/GlobalSuppressions.cs new file mode 100644 index 0000000000..dac8ea36d1 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/GlobalSuppressions.cs @@ -0,0 +1,8 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +using System.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Reliability", "CA2007:Do not directly await a Task", Justification = "Discouraged for VSSDK projects.")] diff --git a/src/GitHub.VisualStudio.UI/Helpers/ThemeDictionaryManager.cs b/src/GitHub.VisualStudio.UI/Helpers/ThemeDictionaryManager.cs index 4ff54b5818..36b3cf10d1 100644 --- a/src/GitHub.VisualStudio.UI/Helpers/ThemeDictionaryManager.cs +++ b/src/GitHub.VisualStudio.UI/Helpers/ThemeDictionaryManager.cs @@ -5,18 +5,15 @@ using GitHub.VisualStudio.Helpers; using GitHub.UI.Helpers; +#pragma warning disable CA1010 // Collections should implement generic interface + namespace GitHub.VisualStudio.UI.Helpers { public class ThemeDictionaryManager : SharedDictionaryManager, IDisposable { - static bool isInDesignMode; + static bool isInDesignMode = DesignerProperties.GetIsInDesignMode(new DependencyObject()); Uri baseThemeUri; - static ThemeDictionaryManager() - { - isInDesignMode = DesignerProperties.GetIsInDesignMode(new DependencyObject()); - } - public override Uri Source { get { return base.Source; } diff --git a/src/GitHub.VisualStudio.UI/Properties/DesignTimeResources.xaml b/src/GitHub.VisualStudio.UI/Properties/DesignTimeResources.xaml new file mode 100644 index 0000000000..10e06ccb27 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Properties/DesignTimeResources.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.UI/SharedDictionary.xaml b/src/GitHub.VisualStudio.UI/SharedDictionary.xaml index 852fdede50..b73034a15b 100644 --- a/src/GitHub.VisualStudio.UI/SharedDictionary.xaml +++ b/src/GitHub.VisualStudio.UI/SharedDictionary.xaml @@ -2,21 +2,24 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:PresentationOptions="http://schemas.microsoft.com/winfx/2006/xaml/presentation/options" xmlns:theme="clr-namespace:GitHub.VisualStudio.UI.Helpers" - xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" - xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI"> + xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI"> + + + + diff --git a/src/GitHub.UI/Assets/Controls/FilterTextBox.xaml b/src/GitHub.VisualStudio.UI/Styles/FilterTextBox.xaml similarity index 90% rename from src/GitHub.UI/Assets/Controls/FilterTextBox.xaml rename to src/GitHub.VisualStudio.UI/Styles/FilterTextBox.xaml index dc1708811c..f8607edc50 100644 --- a/src/GitHub.UI/Assets/Controls/FilterTextBox.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/FilterTextBox.xaml @@ -1,11 +1,12 @@  + xmlns:ui="clr-namespace:GitHub.UI;assembly=GitHub.UI" + xmlns:vs="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0"> @@ -42,14 +43,17 @@ + diff --git a/src/GitHub.UI/Controls/Buttons/OcticonButton.xaml b/src/GitHub.VisualStudio.UI/Styles/OcticonButton.xaml similarity index 98% rename from src/GitHub.UI/Controls/Buttons/OcticonButton.xaml rename to src/GitHub.VisualStudio.UI/Styles/OcticonButton.xaml index dfbb963c87..e3d2e49ad5 100644 --- a/src/GitHub.UI/Controls/Buttons/OcticonButton.xaml +++ b/src/GitHub.VisualStudio.UI/Styles/OcticonButton.xaml @@ -1,8 +1,8 @@ - + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/CommonControlsCheckBoxStyle.xaml b/src/GitHub.VisualStudio.UI/Themes/CommonControlsCheckBoxStyle.xaml new file mode 100644 index 0000000000..53720fe62a --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/CommonControlsCheckBoxStyle.xaml @@ -0,0 +1,105 @@ + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/CommonControlsComboBoxStyle.xaml b/src/GitHub.VisualStudio.UI/Themes/CommonControlsComboBoxStyle.xaml new file mode 100644 index 0000000000..334110e0f2 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/CommonControlsComboBoxStyle.xaml @@ -0,0 +1,374 @@ + + + M 0 0 L 3 3 L 6 0 Z + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/CommonControlsTextBoxStyle.xaml b/src/GitHub.VisualStudio.UI/Themes/CommonControlsTextBoxStyle.xaml new file mode 100644 index 0000000000..d1e926fb76 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/CommonControlsTextBoxStyle.xaml @@ -0,0 +1,34 @@ + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/Dark/CommonControlsColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Dark/CommonControlsColors.xaml new file mode 100644 index 0000000000..d70cccd429 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Dark/CommonControlsColors.xaml @@ -0,0 +1,198 @@ + + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF555555 + #FF555555 + #FF007ACC + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF007ACC + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF656565 + #FF656565 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF1F1F20 + #FF1F1F20 + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF999999 + #FF999999 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF999999 + #FF999999 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF333337 + #FF333337 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF434346 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF999999 + #FF999999 + #FF333337 + #FF333337 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF656565 + #FF656565 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1F1F1C + #FF1F1F1C + #19000000 + #19000000 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF333337 + #FF333337 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #66007ACC + #66007ACC + #FFFFFFFF + #FFFFFFFF + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF555555 + #FF555555 + #FF555555 + #FF555555 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF333337 + #FF333337 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF434346 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FFFFFFFF + #FFFFFFFF + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/Dark/EnvironmentColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Dark/EnvironmentColors.xaml new file mode 100644 index 0000000000..03e417f48f --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Dark/EnvironmentColors.xaml @@ -0,0 +1,1921 @@ + + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FF000000 + #FF000000 + #FF999999 + #FF999999 + #80525252 + #80525252 + #FF656565 + #FF656565 + #80525252 + #80525252 + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E42 + #FF3E3E42 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF0097FB + #FF0097FB + #FFD0D0D0 + #FFD0D0D0 + #FF000000 + #FF000000 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF464646 + #FF464646 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF0F2F9 + #FFF0F2F9 + #FFD3DCEF + #FFD3DCEF + #FFCCCC66 + #FFCCCC66 + #FFFFFFCC + #FFFFFFCC + #FF000000 + #FF000000 + #FFD2D2D2 + #FFD2D2D2 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF00008B + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFF7F0F0 + #FFF7F0F0 + #FFEDDADC + #FFEDDADC + #FFFFFFFF + #FFFFFFFF + #FF0054E3 + #FF0054E3 + #FFDDD6EF + #FFDDD6EF + #FF266035 + #FF266035 + #FFFFFFFF + #FFFFFFFF + #FF716F64 + #FF716F64 + #FFF3F7F0 + #FFF3F7F0 + #FFE6F0DB + #FFE6F0DB + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFB0764F + #FFB0764F + #FF716F64 + #FF716F64 + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFD8D8D8 + #FFD8D8D8 + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFD6ECEF + #FFD6ECEF + #FFFF0000 + #FFFF0000 + #FFF8F4E9 + #FFF8F4E9 + #FFF0E9D2 + #FFF0E9D2 + #FF333337 + #FF333337 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF434346 + #FF434346 + #FF434346 + #FF434346 + #FF656565 + #FF656565 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF333337 + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FF656565 + #FF656565 + #FFF1F1F1 + #FFF1F1F1 + #FF46464A + #FF46464A + #FF46464A + #FF46464A + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3399FF + #FF3399FF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF333337 + #FF999999 + #FF999999 + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FF1B1B1C + #FF1B1B1C + #FF333334 + #FF333334 + #FFF1F1F1 + #FFF1F1F1 + #FF333334 + #FF333334 + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF333337 + #FF333337 + #FF007ACC + #FF007ACC + #FF999999 + #FF999999 + #FF333337 + #FF333337 + #FF999999 + #FF999999 + #FFFFFFFF + #FFFFFFFF + #FF999999 + #FF999999 + #FF0097FB + #FF0097FB + #FF999999 + #FF999999 + #FF999999 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #72555555 + #72555555 + #72555555 + #72555555 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF3399FF + #FF3399FF + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF434346 + #FF434346 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF333337 + #FF333337 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FF656565 + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF222222 + #FF222222 + #FF46464A + #FF46464A + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FFFEFCC8 + #FFFEFCC8 + #FF555555 + #FF555555 + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF333337 + #FF333337 + #003F3F46 + #003F3F46 + #FF424245 + #FF424245 + #FF4D4D50 + #FF4D4D50 + #FF505051 + #FF505051 + #FFF1F1F1 + #FFF1F1F1 + #FF333337 + #FF333337 + #FFF1F1F1 + #FFF1F1F1 + #FF2C2C2F + #FF2C2C2F + #FF37373A + #FF37373A + #FF3D3D3F + #FF3D3D3F + #FF7A7A7A + #FF7A7A7A + #FF333337 + #FF333337 + #FF656565 + #FF656565 + #FF252526 + #FF252526 + #FF46464A + #FF46464A + #FF3F3F46 + #FF3F3F46 + #FF656565 + #FF656565 + #FFFFFFFF + #FFFFFFFF + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FFF2F4F8 + #FFF2F4F8 + #FF000000 + #FF000000 + #FF4A6184 + #FF4A6184 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFBCC7D8 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFF0F0F0 + #FFF0F0F0 + #FF000000 + #FF000000 + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF007ACC + #FF007ACC + #FF333337 + #FF333337 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FF1F1F20 + #FF1F1F20 + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF434346 + #FF434346 + #FF434346 + #FF434346 + #FF656565 + #FF656565 + #FF999999 + #FF999999 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF434346 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF434346 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF333337 + #FFF1F1F1 + #FFF1F1F1 + #72000000 + #72000000 + #FF2D2D30 + #FF2D2D30 + #FF333337 + #FF333337 + #FF252526 + #FF252526 + #FF0097FB + #FF0097FB + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFFF8C00 + #FFFF8C00 + #FFFF8C00 + #FFFF8C00 + #FF656565 + #FF656565 + #FF656565 + #FF656565 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FFF1F1F1 + #FFF1F1F1 + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFFFFFFF + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFFFFFFF + #FF555555 + #FF555555 + #FF555555 + #FF555555 + #FFF1F1F1 + #FFF1F1F1 + #FF442359 + #FF442359 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FF442359 + #FF442359 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FF1B1B1C + #FFF1F1F1 + #FFF1F1F1 + #FFE1D3E4 + #FFE1D3E4 + #FFB064AB + #FFB064AB + #FFB064AB + #FFB064AB + #FFFFFFFF + #FFFFFFFF + #FF9B4F96 + #FF9B4F96 + #FF9B4F96 + #FF9B4F96 + #FFFFFFFF + #FFFFFFFF + #FF555555 + #FF555555 + #FF555555 + #FF555555 + #FFF1F1F1 + #FFF1F1F1 + #FF555555 + #FF555555 + #FFE1D3E4 + #FFE1D3E4 + #FF6D6D70 + #FF6D6D70 + #FFD0E6F5 + #FFD0E6F5 + #FF6D6D70 + #FF6D6D70 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF1C97EA + #FF1C97EA + #FFD0E6F5 + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF68217A + #FF68217A + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF999999 + #FF999999 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF0066CC + #FF0066CC + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF0066CC + #FF0066CC + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFA8B3C2 + #FFA8B3C2 + #FFFFFFFF + #FFFFFFFF + #FFA8B3C2 + #FFA8B3C2 + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF000000 + #FF000000 + #FFA8B3C2 + #FFA8B3C2 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF1B293E + #FF1B293E + #FF0066CC + #FF0066CC + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FFF0F0F0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FF2D2D30 + #FF2D2D30 + #FF7AC1FF + #FF7AC1FF + #FFC8C8C8 + #FFC8C8C8 + #00000000 + #00000000 + #FF3F3F46 + #FF3F3F46 + + + #FF2D2D30 + #FF2D2D30 + #FF656565 + #FF656565 + #FFFEFCC8 + #FFFEFCC8 + #FF1E1E1E + #FF1E1E1E + #001E1E1E + #001E1E1E + #00F1F1F1 + #00F1F1F1 + #FF0E639C + #FF0E639C + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FFCA5100 + #FFCA5100 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #FFFFFFFF + #FFFFFFFF + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #FF434346 + #FF434346 + #FF2D2D30 + #FF2D2D30 + #99999999 + #99999999 + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #FFFFFFFF + #FFFFFFFF + #7F000000 + #7F000000 + #FF333337 + #FF333337 + #002D2D30 + #002D2D30 + #FF1B1B1C + #FF1B1B1C + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF333337 + #FF333337 + #FF3F3F46 + #FF3F3F46 + #FF7D7D7D + #FF7D7D7D + #FF3399FF + #FF3399FF + #FFC6C6C6 + #FFC6C6C6 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF0097FB + #FF0097FB + #FF656565 + #FF656565 + #FF55AAFF + #FF55AAFF + #FFFFFFFF + #FFFFFFFF + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF0097FB + #FF0097FB + #FF0097FB + #FF0097FB + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF2D2D30 + #FF2D2D30 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF333337 + #FF333337 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #FFFFFFFF + #FFFFFFFF + #72555555 + #72555555 + #72555555 + #72555555 + #FFFFFFFF + #FFFFFFFF + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #66FFFFFF + #FFFEFCC8 + #FFFEFCC8 + #FFFEFCC8 + #FFFEFCC8 + #FF252526 + #FF252526 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF999999 + #FF999999 + #FF555558 + #FF555558 + #FF1C97EA + #FF1C97EA + #FF007ACC + #FF007ACC + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF1C1C1C + #FF1C1C1C + #FF3E3E42 + #FF3E3E42 + #FF686868 + #FF686868 + #FF686868 + #FF686868 + #FF3E3E42 + #FF3E3E42 + #FF686868 + #FF686868 + #FF9E9E9E + #FF9E9E9E + #FFEFEBEF + #FFEFEBEF + #FF9E9E9E + #FF9E9E9E + #FF9E9E9E + #FF9E9E9E + #FFEFEBEF + #FFEFEBEF + #FFEFEBEF + #FFEFEBEF + #FF333337 + #FF333337 + #FF333337 + #FF333337 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FFE5C365 + #FFE5C365 + #FFFFEFBB + #FFFFEFBB + #FFE5C365 + #FFE5C365 + #FFFEFCC8 + #FFFEFCC8 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF4169E1 + #FF4169E1 + #FF96A9DD + #FF96A9DD + #FFE122DF + #FFE122DF + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF434346 + #FF434346 + #FF1F1F22 + #FF1F1F22 + #FF1F1F22 + #FF1F1F22 + #FF999999 + #FF999999 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FFF30506 + #FFF30506 + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FFF30506 + #FFF30506 + #FFF30506 + #FFF30506 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF363639 + #FF363639 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF28282B + #FF28282B + #FF28282B + #FF28282B + #FFF1F1F1 + #FFF1F1F1 + #FFF30506 + #FFF30506 + #FFF1F1F1 + #FFF1F1F1 + #FF0097FB + #FF0097FB + #FF88CCFE + #FF88CCFE + #FFF1F1F1 + #FFF1F1F1 + #FF999999 + #FF999999 + #FF55AAFF + #FF55AAFF + #FFF1F1F1 + #FFF1F1F1 + #FF999999 + #FF999999 + #FF55AAFF + #FF55AAFF + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F3F + #FF3F3F3F + #FF464646 + #FF464646 + #FF999999 + #FF999999 + #FF0E639C + #FF0E639C + #FFFFFFFF + #FFFFFFFF + #FFCA5100 + #FFCA5100 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #4C000000 + #4C000000 + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF464646 + #FF464646 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF3F3F46 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF46464A + #FF46464A + #FF59A8DE + #FF59A8DE + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFD0D0D0 + #FFD0D0D0 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF656565 + #FF656565 + #FF3E3E40 + #FF3E3E40 + #FF656565 + #FF656565 + #FF333337 + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF424245 + #FF424245 + #FFF1F1F1 + #FFF1F1F1 + #FF4D4D50 + #FF4D4D50 + #FF717171 + #FF717171 + #FF252526 + #FF252526 + #FF3F3F46 + #FF3F3F46 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFFFFFFF + #FF393939 + #FF393939 + #FF393939 + #FF393939 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FF2D2D30 + #FF2D2D30 + #99999999 + #99999999 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF55AAFF + #FF55AAFF + #FF0097FB + #FF0097FB + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF0097FB + #FF0097FB + #FF3F3F46 + #FF3F3F46 + #FFD0D0D0 + #FFD0D0D0 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF705829 + #FF705829 + #FFB0A781 + #FFB0A781 + #FFA19667 + #FFA19667 + #FFA79432 + #FFA79432 + #FFD0D4B7 + #FFD0D4B7 + #FFBFC749 + #FFBFC749 + #FFCAB22D + #FFCAB22D + #FFFBF7C8 + #FFFBF7C8 + #FFE2E442 + #FFE2E442 + #FF5D8039 + #FF5D8039 + #FFB1C97B + #FFB1C97B + #FF9FB861 + #FF9FB861 + #FF8E5478 + #FF8E5478 + #FFE2B1CD + #FFE2B1CD + #FFCB98B6 + #FFCB98B6 + #FFAD1C2B + #FFAD1C2B + #FFFF9F99 + #FFFF9F99 + #FFFF7971 + #FFFF7971 + #FF779AB6 + #FF779AB6 + #FFC6D4DF + #FFC6D4DF + #FFB8CCD7 + #FFB8CCD7 + #FF427094 + #FF427094 + #FFA0B7C9 + #FFA0B7C9 + #FF89ABBD + #FF89ABBD + #FF5386BF + #FF5386BF + #FFB9D4EE + #FFB9D4EE + #FFA1C7E7 + #FFA1C7E7 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + + + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/Dark/ThemedDialogColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Dark/ThemedDialogColors.xaml new file mode 100644 index 0000000000..e5c559dbe3 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Dark/ThemedDialogColors.xaml @@ -0,0 +1,66 @@ + + #00000000 + #00000000 + #00000000 + #00000000 + #FFF1F1F1 + #FFF1F1F1 + #FF999999 + #FF999999 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF0097FB + #FF0097FB + #FF656565 + #FF656565 + #FF55AAFF + #FF55AAFF + #FF0097FB + #FF0097FB + #FF656565 + #FF656565 + #FF3F3F40 + #FF3F3F40 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #00000000 + #00000000 + #00000000 + #00000000 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FFF1F1F1 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/Dark/TreeViewColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Dark/TreeViewColors.xaml new file mode 100644 index 0000000000..5bd259ebcd --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Dark/TreeViewColors.xaml @@ -0,0 +1,48 @@ + + #FF252526 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FFE51400 + #FFE51400 + #FFFFFFFF + #FFFFFFFF + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFFEFCC8 + #FFFEFCC8 + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFE51400 + #FFE51400 + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/Dark/VsBrushes.xaml b/src/GitHub.VisualStudio.UI/Themes/Dark/VsBrushes.xaml new file mode 100644 index 0000000000..399f40ffd1 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Dark/VsBrushes.xaml @@ -0,0 +1,826 @@ + + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF0097FB + #FFD0D0D0 + #FF000000 + #FF2D2D30 + #FF3F3F46 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF464646 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF0F2F9 + #FFD3DCEF + #FFCCCC66 + #FFFFFFCC + #FF000000 + #FFD2D2D2 + #FF808080 + #FF000000 + #FFFFFFFF + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFF7F0F0 + #FFEDDADC + #FFFFFFFF + #FF0054E3 + #FFDDD6EF + #FF266035 + #FFFFFFFF + #FF716F64 + #FFF3F7F0 + #FFE6F0DB + #FF808080 + #FF716F64 + #FFB0764F + #FF716F64 + #FF808080 + #FF716F64 + #FFD8D8D8 + #FF808080 + #FF716F64 + #FFD6ECEF + #FFFF0000 + #FFF8F4E9 + #FFF0E9D2 + #FF333337 + #FF434346 + #FF2D2D30 + #FF434346 + #FF434346 + #FFF1F1F1 + #FF3F3F46 + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF2D2D30 + #FF999999 + #FF46464A + #FF46464A + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3399FF + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF1B1B1C + #FF007ACC + #FF333337 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF2D2D30 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #72555555 + #72555555 + #72555555 + #72555555 + #FF007ACC + #FF2D2D30 + #FF3399FF + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FFF1F1F1 + #FF2D2D30 + #FF222222 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FFFEFCC8 + #FF555555 + #FF0097FB + #FF0097FB + #FF0097FB + #FF333337 + #FF424245 + #FF4D4D50 + #FF505051 + #FFF1F1F1 + #FF333337 + #FFF1F1F1 + #FF2C2C2F + #FF37373A + #FF3D3D3F + #FF7A7A7A + #FF333337 + #FF656565 + #FF252526 + #FF46464A + #FF3F3F46 + #FF656565 + #FFFFFFFF + #FFF2F4F8 + #FF000000 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFFFFFFF + #FF000000 + #FF1B1B1C + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF007ACC + #FF333337 + #FF434346 + #FF2D2D30 + #FF434346 + #FF434346 + #FF999999 + #FF3F3F46 + #FF434346 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF007ACC + #FF1B1B1C + #FF1B1B1C + #FF333337 + #72000000 + #FF333337 + #FF252526 + #FF0097FB + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFFF8C00 + #FFFF8C00 + #FF656565 + #FF656565 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF1C97EA + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFF1F1F1 + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF999999 + #FF2D2D30 + #FFF1F1F1 + #FF000000 + #FFDEE1E7 + #FF0066CC + #FF000000 + #FFFFFFFF + #FF0066CC + #FF000000 + #FFFFFFFF + #FFA8B3C2 + #FFFFFFFF + #FFA8B3C2 + #FF000000 + #FFDEE1E7 + #FF000000 + #FFA8B3C2 + #FF000000 + #FFFFFFFF + #FF000000 + #FFDEE1E7 + #FF1B293E + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FF000000 + #FF000000 + #FF3399FF + #FFFFFFFF + #FF3F3F46 + #FF2D2D30 + #FF656565 + #FFFEFCC8 + #FF1E1E1E + #FF333337 + #FF1B1B1C + #FFF1F1F1 + #FF252526 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3399FF + #FF3399FF + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF252526 + #FFF1F1F1 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FF333337 + #FF252526 + #FF252526 + #FF3E3E40 + #FF3E3E40 + #FF0097FB + #FF55AAFF + #FF0097FB + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FF252526 + #FFF1F1F1 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF2D2D30 + #FF252526 + #FF252526 + #FF2D2D30 + #FFFEFCC8 + #FFFEFCC8 + #FF252526 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF686868 + #FF686868 + #FF686868 + #FF9E9E9E + #FFEFEBEF + #FF333337 + #FF333337 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFE5C365 + #FFFFEFBB + #FFE5C365 + #FFFEFCC8 + #FF000000 + #FF000000 + #FF4169E1 + #FF96A9DD + #FFE122DF + #FF252526 + #FFF1F1F1 + #FF434346 + #FF1F1F22 + #FF1F1F22 + #FF999999 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FFF30506 + #FF0097FB + #FF55AAFF + #FFF30506 + #FF007ACC + #FFFFFFFF + #FF363639 + #FF252526 + #FF252526 + #FF28282B + #FF28282B + #FFF1F1F1 + #FFF30506 + #FFF1F1F1 + #FF0097FB + #FF88CCFE + #FFF1F1F1 + #FF999999 + #FF55AAFF + #FFF1F1F1 + #FF999999 + #FF55AAFF + #FFF1F1F1 + #FF3F3F3F + #FF464646 + #FF999999 + #FFFFFFFF + #FF000000 + #FF2D2D30 + #FF3F3F46 + #FF464646 + #FF2D2D30 + #FF3F3F46 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFD0D0D0 + #FF252526 + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF252526 + #FF3F3F46 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FF393939 + #FF393939 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF55AAFF + #FF252526 + #FF0097FB + #FFD0D0D0 + #FFF1F1F1 + #FF705829 + #FFB0A781 + #FFA19667 + #FFA79432 + #FFD0D4B7 + #FFBFC749 + #FFCAB22D + #FFFBF7C8 + #FFE2E442 + #FF5D8039 + #FFB1C97B + #FF9FB861 + #FF8E5478 + #FFE2B1CD + #FFCB98B6 + #FFAD1C2B + #FFFF9F99 + #FFFF7971 + #FF779AB6 + #FFC6D4DF + #FFB8CCD7 + #FF427094 + #FFA0B7C9 + #FF89ABBD + #FF5386BF + #FFB9D4EE + #FFA1C7E7 + #FF252526 + #FF2D2D30 + #FFF1F1F1 + #FFFFFFFF + #FF000000 + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.UI/Themes/Dark/VsColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Dark/VsColors.xaml new file mode 100644 index 0000000000..d3abe4d276 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Dark/VsColors.xaml @@ -0,0 +1,505 @@ + + #FF3F3F46 + #FF3F3F46 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3F3F46 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF0097FB + #FFD0D0D0 + #FF000000 + #FF2D2D30 + #FF3F3F46 + #FF252526 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF464646 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FFF0F2F9 + #FFD3DCEF + #FFCCCC66 + #FFFFFFCC + #FF000000 + #FFD2D2D2 + #FF808080 + #FF000000 + #FFFFFFFF + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFF7F0F0 + #FFEDDADC + #FFFFFFFF + #FF0054E3 + #FFDDD6EF + #FF266035 + #FFFFFFFF + #FF716F64 + #FFF3F7F0 + #FFE6F0DB + #FF808080 + #FF716F64 + #FFB0764F + #FF716F64 + #FF808080 + #FF716F64 + #FFD8D8D8 + #FF808080 + #FF716F64 + #FFD6ECEF + #FFFF0000 + #FFF8F4E9 + #FFF0E9D2 + #FF333337 + #FF434346 + #FF2D2D30 + #FF434346 + #FF434346 + #FFF1F1F1 + #FF3F3F46 + #FF007ACC + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF007ACC + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF2D2D30 + #FF999999 + #FF46464A + #FF46464A + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3399FF + #FF1B1B1C + #FF1B1B1C + #FF333337 + #FF1B1B1C + #FF007ACC + #FF333337 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF2D2D30 + #FF999999 + #FF007ACC + #FF007ACC + #FF007ACC + #72555555 + #72555555 + #72555555 + #72555555 + #FF007ACC + #FF2D2D30 + #FF3399FF + #FF2D2D30 + #FFF1F1F1 + #FFF1F1F1 + #FF656565 + #FFF1F1F1 + #FF2D2D30 + #FF222222 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FFFEFCC8 + #FF555555 + #FF0097FB + #FF0097FB + #FF0097FB + #FF333337 + #FF424245 + #FF4D4D50 + #FF505051 + #FFF1F1F1 + #FF333337 + #FFF1F1F1 + #FF2C2C2F + #FF37373A + #FF3D3D3F + #FF7A7A7A + #FF333337 + #FF656565 + #FF252526 + #FF46464A + #FF3F3F46 + #FF656565 + #FFFFFFFF + #FFF2F4F8 + #FF000000 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFFFFFFF + #FF000000 + #FF1B1B1C + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FF252526 + #FF252526 + #FF007ACC + #FF333337 + #FF434346 + #FF2D2D30 + #FF434346 + #FF434346 + #FF999999 + #FF3F3F46 + #FF434346 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF434346 + #FF007ACC + #FF1B1B1C + #FF1B1B1C + #FF333337 + #72000000 + #FF333337 + #FF252526 + #FF0097FB + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFFF8C00 + #FFFF8C00 + #FF656565 + #FF656565 + #FF2D2D30 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF2D2D30 + #FF2D2D30 + #FF1C97EA + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFF1F1F1 + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF999999 + #FF2D2D30 + #FFF1F1F1 + #FF000000 + #FFDEE1E7 + #FF0066CC + #FF000000 + #FFFFFFFF + #FF0066CC + #FF000000 + #FFFFFFFF + #FFA8B3C2 + #FFFFFFFF + #FFA8B3C2 + #FF000000 + #FFDEE1E7 + #FF000000 + #FFA8B3C2 + #FF000000 + #FFFFFFFF + #FF000000 + #FFDEE1E7 + #FF1B293E + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FF000000 + #FF000000 + #FF3399FF + #FFFFFFFF + #FF3F3F46 + #FF2D2D30 + #FF656565 + #FFFEFCC8 + #FF1E1E1E + #FF333337 + #FF1B1B1C + #FFF1F1F1 + #FF252526 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3399FF + #FF3399FF + #FF3E3E40 + #FF3E3E40 + #FFF1F1F1 + #FF3E3E40 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FFF1F1F1 + #FF3F3F46 + #FF252526 + #FFF1F1F1 + #FF3E3E40 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FFF1F1F1 + #FF333337 + #FF252526 + #FF252526 + #FF3E3E40 + #FF3E3E40 + #FF0097FB + #FF55AAFF + #FF0097FB + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FF252526 + #FFF1F1F1 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF2D2D30 + #FF252526 + #FF252526 + #FF2D2D30 + #FFFEFCC8 + #FFFEFCC8 + #FF252526 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF3E3E42 + #FF686868 + #FF686868 + #FF686868 + #FF9E9E9E + #FFEFEBEF + #FF333337 + #FF333337 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF3F3F46 + #FF007ACC + #FF252526 + #FF252526 + #FF252526 + #FFF1F1F1 + #FFE5C365 + #FFFFEFBB + #FFE5C365 + #FFFEFCC8 + #FF000000 + #FF000000 + #FF4169E1 + #FF96A9DD + #FFE122DF + #FF252526 + #FFF1F1F1 + #FF434346 + #FF1F1F22 + #FF1F1F22 + #FF999999 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FF464646 + #FFF30506 + #FF0097FB + #FF55AAFF + #FFF30506 + #FF007ACC + #FFFFFFFF + #FF363639 + #FF252526 + #FF252526 + #FF28282B + #FF28282B + #FFF1F1F1 + #FFF30506 + #FFF1F1F1 + #FF0097FB + #FF88CCFE + #FFF1F1F1 + #FF999999 + #FF55AAFF + #FFF1F1F1 + #FF999999 + #FF55AAFF + #FFF1F1F1 + #FF3F3F3F + #FF464646 + #FF999999 + #FFFFFFFF + #FF000000 + #FF2D2D30 + #FF3F3F46 + #FF464646 + #FF2D2D30 + #FF3F3F46 + #FF2D2D30 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FFD0D0D0 + #FF252526 + #FF333337 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF252526 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF252526 + #FF3F3F46 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FF393939 + #FF393939 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FFF1F1F1 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF3E3E40 + #FF3E3E40 + #FF3E3E40 + #FF55AAFF + #FF252526 + #FF0097FB + #FFD0D0D0 + #FFF1F1F1 + #FF705829 + #FFB0A781 + #FFA19667 + #FFA79432 + #FFD0D4B7 + #FFBFC749 + #FFCAB22D + #FFFBF7C8 + #FFE2E442 + #FF5D8039 + #FFB1C97B + #FF9FB861 + #FF8E5478 + #FFE2B1CD + #FFCB98B6 + #FFAD1C2B + #FFFF9F99 + #FFFF7971 + #FF779AB6 + #FFC6D4DF + #FFB8CCD7 + #FF427094 + #FFA0B7C9 + #FF89ABBD + #FF5386BF + #FFB9D4EE + #FFA1C7E7 + #FF252526 + #FF2D2D30 + #FFF1F1F1 + #FFFFFFFF + #FF000000 + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/FontScalingLabelStyle.xaml b/src/GitHub.VisualStudio.UI/Themes/FontScalingLabelStyle.xaml new file mode 100644 index 0000000000..b1cd2df882 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/FontScalingLabelStyle.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/FontScalingTextBlockStyle.xaml b/src/GitHub.VisualStudio.UI/Themes/FontScalingTextBlockStyle.xaml new file mode 100644 index 0000000000..e1d59a1b49 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/FontScalingTextBlockStyle.xaml @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.VisualStudio.UI/Themes/HighContrast/CommonControlsColors.xaml b/src/GitHub.VisualStudio.UI/Themes/HighContrast/CommonControlsColors.xaml new file mode 100644 index 0000000000..f0734228b1 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/HighContrast/CommonControlsColors.xaml @@ -0,0 +1,198 @@ + + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF00002F + #FF00002F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF00002F + #FF00002F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF37006E + #FF37006E + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FFFFFFFF + diff --git a/src/GitHub.VisualStudio.UI/Themes/HighContrast/EnvironmentColors.xaml b/src/GitHub.VisualStudio.UI/Themes/HighContrast/EnvironmentColors.xaml new file mode 100644 index 0000000000..1fde95b09c --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/HighContrast/EnvironmentColors.xaml @@ -0,0 +1,1588 @@ + + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FF37006E + #FF37006E + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFC0C0C0 + #FFC0C0C0 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFF00 + #FFFFFF00 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFFFF + #FFFFFFFF + #00C800C8 + #00C800C8 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF008000 + #FF008000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF000000 + #FF000000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF00529B + #FF00529B + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF008000 + #FF008000 + + + #FF00002F + #FF00002F + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #00C800C8 + #00C800C8 + #00C800C8 + #00C800C8 + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF00002F + #FF00002F + #00000000 + #00000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF00002F + #FF00002F + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #7F000000 + #7F000000 + #FF808080 + #FF808080 + #00C800C8 + #00C800C8 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF008000 + #FF008000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF008000 + #FF008000 + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FF3FF23F + #FF3FF23F + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FFC0C0C0 + #FFC0C0C0 + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF00002F + #FF00002F + #00000000 + #00000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFC0C0C0 + #FFC0C0C0 + #FF00002F + #FF00002F + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFC0C0C0 + #FFC0C0C0 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FFFF00FF + #FFFF00FF + #FFFF00FF + #FFFF00FF + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FF00002F + #FF00002F + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF008000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + + + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.UI/Themes/HighContrast/ThemedDialogColors.xaml b/src/GitHub.VisualStudio.UI/Themes/HighContrast/ThemedDialogColors.xaml new file mode 100644 index 0000000000..fd60c10fe1 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/HighContrast/ThemedDialogColors.xaml @@ -0,0 +1,66 @@ + + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FF808080 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF3FF23F + #FF3FF23F + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FF3FF23F + #FF3FF23F + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF808080 + diff --git a/src/GitHub.VisualStudio.UI/Themes/HighContrast/TreeViewColors.xaml b/src/GitHub.VisualStudio.UI/Themes/HighContrast/TreeViewColors.xaml new file mode 100644 index 0000000000..44f348589e --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/HighContrast/TreeViewColors.xaml @@ -0,0 +1,48 @@ + + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + #FFFFFF00 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFF00 + #FFFFFF00 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF37006E + #FF37006E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF3FF23F + #FF3FF23F + diff --git a/src/GitHub.VisualStudio.UI/Themes/HighContrast/VsBrushes.xaml b/src/GitHub.VisualStudio.UI/Themes/HighContrast/VsBrushes.xaml new file mode 100644 index 0000000000..fa878c33a4 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/HighContrast/VsBrushes.xaml @@ -0,0 +1,554 @@ + + #FF808080 + #FF808080 + #FFC0C0C0 + #FF000000 + #FF000000 + #FFFFFF00 + #FF37006E + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFC0C0C0 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF1AEBFF + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFFFF + #FF000000 + #FF808080 + #FF1AEBFF + #FF000000 + #FF808080 + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFFFF + #FF000000 + #FF808080 + #FF3FF23F + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF008000 + #FF008000 + #FF000000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF808080 + #FF000000 + #FF3FF23F + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF008000 + #FF00002F + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FF008000 + #FF000000 + #FF000000 + #FF008000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF000000 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFC0C0C0 + #FF00002F + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF000000 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FFFFFF00 + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFF00 + #FF1AEBFF + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF008000 + #FFFFFFFF + #FF000000 + #FFC0C0C0 + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFF00FF + #FFFF00FF + #FF37006E + #FF008000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.UI/Themes/HighContrast/VsColors.xaml b/src/GitHub.VisualStudio.UI/Themes/HighContrast/VsColors.xaml new file mode 100644 index 0000000000..1ccb3ea8e0 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/HighContrast/VsColors.xaml @@ -0,0 +1,505 @@ + + #FF808080 + #FF808080 + #FFC0C0C0 + #FF000000 + #FF000000 + #FFFFFF00 + #FF37006E + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFC0C0C0 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF000000 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3FF23F + #FF1AEBFF + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FFFFFFFF + #FF000000 + #FF808080 + #FF1AEBFF + #FF000000 + #FF808080 + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFFFF + #FF000000 + #FF808080 + #FF3FF23F + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FFFFFFFF + #FF000000 + #FF3FF23F + #FF3FF23F + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF008000 + #FF008000 + #FFFFFFFF + #FFFFFFFF + #FF008000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF008000 + #FF008000 + #FF000000 + #FF008000 + #FF008000 + #FF008000 + #FF008000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF808080 + #FF000000 + #FF3FF23F + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF008000 + #FF00002F + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF808080 + #FF000000 + #FF000000 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FF008000 + #FF000000 + #FF000000 + #FF008000 + #FF000000 + #FFFFFF00 + #FFFFFF00 + #FFFFFF00 + #FF808080 + #FF808080 + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF808080 + #FF000000 + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FFC0C0C0 + #FFC0C0C0 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF808080 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFC0C0C0 + #FF00002F + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF808080 + #FF000000 + #FF000000 + #FFFFFF00 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FFFFFF00 + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF1AEBFF + #FFFFFF00 + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FFFFFFFF + #FF000000 + #FF808080 + #FFFFFF00 + #FF1AEBFF + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FFFFFFFF + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF008000 + #FFFFFFFF + #FF000000 + #FFC0C0C0 + #FFFFFFFF + #FF808080 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF808080 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FF1AEBFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFF00FF + #FFFF00FF + #FF37006E + #FF008000 + #FF000000 + #FF000000 + #FF1AEBFF + #FF1AEBFF + #FF808080 + #FF000000 + #FF000000 + #FF1AEBFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FFFFFFFF + diff --git a/src/GitHub.VisualStudio.UI/Themes/Light/CommonControlsColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Light/CommonControlsColors.xaml new file mode 100644 index 0000000000..ad69cd171e --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Light/CommonControlsColors.xaml @@ -0,0 +1,198 @@ + + #FFECECF0 + #FFECECF0 + #FF1E1E1E + #FF1E1E1E + #FFACACAC + #FFACACAC + #FF3399FF + #FF3399FF + #FFCCCEDB + #FFCCCEDB + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF007ACC + #FF007ACC + #FFECECF0 + #FFECECF0 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFA2A4A5 + #FFA2A4A5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFEFEFE + #FFFEFEFE + #FFF6F6F6 + #FFF6F6F6 + #FFF3F9FF + #FFF3F9FF + #FFF3F9FF + #FFF3F9FF + #FF007ACC + #FF007ACC + #FF717171 + #FF717171 + #FFC6C6C6 + #FFC6C6C6 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FF717171 + #FF717171 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF717171 + #FF717171 + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFF6F6F6 + #FFF6F6F6 + #19000000 + #19000000 + #FFCCCEDB + #FFCCCEDB + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #66007ACC + #66007ACC + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FF1E1E1E + #FF1E1E1E + diff --git a/src/GitHub.VisualStudio.UI/Themes/Light/EnvironmentColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Light/EnvironmentColors.xaml new file mode 100644 index 0000000000..6ad0ec8bbf --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Light/EnvironmentColors.xaml @@ -0,0 +1,1588 @@ + + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF525252 + #FF525252 + #FFFFFFFF + #FFFFFFFF + #FF525252 + #FF525252 + #80525252 + #80525252 + #FFFFFFFF + #FFFFFFFF + #80525252 + #80525252 + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF0E70C0 + #FF0E70C0 + #FF444444 + #FF444444 + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEBD + #FFCCCEBD + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FFD8D8E0 + #FFD8D8E0 + #FFCCCEBD + #FFCCCEBD + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFF0F2F9 + #FFF0F2F9 + #FFD3DCEF + #FFD3DCEF + #FFCCCC66 + #FFCCCC66 + #FFFFFFCC + #FFFFFFCC + #FF000000 + #FF000000 + #FFD2D2D2 + #FFD2D2D2 + #FF808080 + #FF808080 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF00008B + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFF7F0F0 + #FFF7F0F0 + #FFEDDADC + #FFEDDADC + #FFFFFFFF + #FFFFFFFF + #FF0054E3 + #FF0054E3 + #FFDDD6EF + #FFDDD6EF + #FF266035 + #FF266035 + #FFFFFFFF + #FFFFFFFF + #FF716F64 + #FF716F64 + #FFF3F7F0 + #FFF3F7F0 + #FFE6F0DB + #FFE6F0DB + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFB0764F + #FFB0764F + #FF716F64 + #FF716F64 + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFD8D8D8 + #FFD8D8D8 + #FF808080 + #FF808080 + #FF716F64 + #FF716F64 + #FFD6ECEF + #FFD6ECEF + #FFFF0000 + #FFFF0000 + #FFF8F4E9 + #FFF8F4E9 + #FFF0E9D2 + #FFF0E9D2 + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFA2A4A5 + #FFA2A4A5 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF717171 + #FF717171 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FFA2A4A5 + #FFA2A4A5 + #FF1E1E1E + #FF1E1E1E + #FF999999 + #FF999999 + #FF999999 + #FF999999 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF3399FF + #FF3399FF + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FF0E70C0 + #FF0E70C0 + #FF007ACC + #FF007ACC + #FFF6F6F6 + #FFF6F6F6 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFC9DEF5 + #FFC9DEF5 + #FF0E70C0 + #FF0E70C0 + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFE0E3E6 + #FFE0E3E6 + #FF007ACC + #FF007ACC + #FF717171 + #FF717171 + #FFE0E3E6 + #FFE0E3E6 + #FF717171 + #FF717171 + #FFFFFFFF + #FFFFFFFF + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF717171 + #FF717171 + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF3399FF + #FF3399FF + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FFA2A4A5 + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FFFDFBAC + #FFFDFBAC + #FF717171 + #FF717171 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FFCCCEDB + #FFCCCEDB + #00CCCEDB + #00CCCEDB + #FFE7E8EC + #FFE7E8EC + #FFCCCEDB + #FFCCCEDB + #FFEDEEF0 + #FFEDEEF0 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFD6D8DC + #FFD6D8DC + #FFCCCEDB + #FFCCCEDB + #FFEDEEF0 + #FFEDEEF0 + #FFA2A4A5 + #FFA2A4A5 + #FFCCCEDB + #FFCCCEDB + #FFA2A4A5 + #FFA2A4A5 + #FFF5F5F5 + #FFF5F5F5 + #FF999999 + #FF999999 + #FFCCCEDB + #FFCCCEDB + #FFA2A4A5 + #FFA2A4A5 + #FFFFFFFF + #FFFFFFFF + #FF0E70C0 + #FF0E70C0 + #FF007ACC + #FF007ACC + #FFF2F4F8 + #FFF2F4F8 + #FF000000 + #FF000000 + #FF4A6184 + #FF4A6184 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFBCC7D8 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFF0F0F0 + #FFF0F0F0 + #FF000000 + #FF000000 + #FFE7E8EC + #FFE7E8EC + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFA2A4A5 + #FFA2A4A5 + #FF717171 + #FF717171 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #72000000 + #72000000 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FF0E70C0 + #FF0E70C0 + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFFFA300 + #FFFFA300 + #FFFFA300 + #FFFFA300 + #FFA2A4A5 + #FFA2A4A5 + #FFA2A4A5 + #FFA2A4A5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FFB7B9C5 + #FFB7B9C5 + #FFB7B9C5 + #FFB7B9C5 + #FF2D2D2D + #FF2D2D2D + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFFFFFFF + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFFFFFFF + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FF717171 + #FF717171 + #FF442359 + #FF442359 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FF442359 + #FF442359 + #FF442359 + #FF442359 + #FFFFFFFF + #FFFFFFFF + #FFB7B9C5 + #FFB7B9C5 + #FFB7B9C5 + #FFB7B9C5 + #FF2D2D2D + #FF2D2D2D + #FFE1D3E4 + #FFE1D3E4 + #FFB064AB + #FFB064AB + #FFB064AB + #FFB064AB + #FFFFFFFF + #FFFFFFFF + #FF9B4F96 + #FF9B4F96 + #FF9B4F96 + #FF9B4F96 + #FFFFFFFF + #FFFFFFFF + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FFE6E7ED + #FF717171 + #FF717171 + #FF717171 + #FF717171 + #FFE1D3E4 + #FFE1D3E4 + #FF6D6D70 + #FF6D6D70 + #FFD0E6F5 + #FFD0E6F5 + #FF6D6D70 + #FF6D6D70 + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF1C97EA + #FF1C97EA + #FFD0E6F5 + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FF717171 + #FF68217A + #FF68217A + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF717171 + #FF717171 + #FFEFEFE2 + #FFEFEFE2 + #FF1E1E1E + #FF1E1E1E + #FFF0F0F0 + #FFF0F0F0 + #FFDEE1E7 + #FFDEE1E7 + #FF0066CC + #FF0066CC + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF0066CC + #FF0066CC + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FFA8B3C2 + #FFA8B3C2 + #FFFFFFFF + #FFFFFFFF + #FFA8B3C2 + #FFA8B3C2 + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF000000 + #FF000000 + #FFA8B3C2 + #FFA8B3C2 + #FF000000 + #FF000000 + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + #FFDEE1E7 + #FFDEE1E7 + #FF1B293E + #FF1B293E + #FF0066CC + #FF0066CC + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FFF0F0F0 + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FF00529B + #FF00529B + #FF424242 + #FF424242 + #00000000 + #00000000 + #FFCCCEDB + #FFCCCEDB + + + #FFEEEEF2 + #FFEEEEF2 + #FFA2A4A5 + #FFA2A4A5 + #FFFDFBAC + #FFFDFBAC + #FF1E1E1E + #FF1E1E1E + #00F5F5F5 + #00F5F5F5 + #001E1E1E + #001E1E1E + #FF9B9FB9 + #FF9B9FB9 + #FFEEEEF2 + #FFEEEEF2 + #FF525252 + #FF525252 + #FF9B9FB9 + #FF9B9FB9 + #FF9B9FB9 + #FF9B9FB9 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF9B9FB9 + #FF9B9FB9 + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #FF007ACC + #FF007ACC + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #FF007ACC + #FF007ACC + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #99525252 + #99525252 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #FFFFFFFF + #FFFFFFFF + #7F000000 + #7F000000 + #FFCCCEDB + #FFCCCEDB + #00EEEEF2 + #00EEEEF2 + #FFF6F6F6 + #FFF6F6F6 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FF1E1E1E + #FF1E1E1E + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFFEFEFE + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FFCCCED8 + #FFCCCED8 + #FF636363 + #FF636363 + #FF3399FF + #FF3399FF + #FFD0D0D0 + #FFD0D0D0 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FFFEFEFE + #FF0E70C0 + #FF0E70C0 + #FFA2A4A5 + #FFA2A4A5 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FFEEEEF2 + #FFEEEEF2 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFFCFCFC + #FFFCFCFC + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #FF865FC5 + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #FF007ACC + #FF007ACC + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #D8FFFFFF + #FF007ACC + #FF007ACC + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #66000000 + #FFFDFBAC + #FFFDFBAC + #FFFDFBAC + #FFFDFBAC + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF868999 + #FF868999 + #FFF5F5F5 + #FFF5F5F5 + #FF1C97EA + #FF1C97EA + #FF007ACC + #FF007ACC + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFFFFFFF + #FFFFFFFF + #FFF5F5F5 + #FFF5F5F5 + #FFC2C3C9 + #FFC2C3C9 + #FFC2C3C9 + #FFC2C3C9 + #FFF5F5F5 + #FFF5F5F5 + #FFC2C3C9 + #FFC2C3C9 + #FF686868 + #FF686868 + #FF5B5B5B + #FF5B5B5B + #FF686868 + #FF686868 + #FF686868 + #FF686868 + #FF5B5B5B + #FF5B5B5B + #FF5B5B5B + #FF5B5B5B + #FFFCFCFC + #FFFCFCFC + #FFFCFCFC + #FFFCFCFC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFE5C365 + #FFE5C365 + #FFFFEFBB + #FFFFEFBB + #FFE5C365 + #FFE5C365 + #FFFDFBAC + #FFFDFBAC + #FF000000 + #FF000000 + #FF000000 + #FF000000 + #FF4169E1 + #FF4169E1 + #FF96A9DD + #FF96A9DD + #FFE122DF + #FFE122DF + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FF999999 + #FF999999 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FF999999 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFF30506 + #FFF30506 + #FF0097FB + #FF0097FB + #FF55AAFF + #FF55AAFF + #FFF30506 + #FFF30506 + #FFF30506 + #FFF30506 + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF363639 + #FF363639 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF1F1F1 + #FFF30506 + #FFF30506 + #FF555555 + #FF555555 + #FF007ACC + #FF007ACC + #FF77AAFF + #FF77AAFF + #FF1E1E1E + #FF1E1E1E + #FF999999 + #FF999999 + #FF007ACC + #FF007ACC + #FF555555 + #FF555555 + #FF999999 + #FF999999 + #FF007ACC + #FF007ACC + #FF000000 + #FF000000 + #FF3F3F3F + #FF3F3F3F + #FF464646 + #FF464646 + #FF999999 + #FF999999 + #FF0E639C + #FF0E639C + #FFFFFFFF + #FFFFFFFF + #FFCA5100 + #FFCA5100 + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #4C000000 + #4C000000 + #FFFFFFFF + #FFFFFFFF + #FF68217A + #FF68217A + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFF0F0F0 + #FFF0F0F0 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FFD8D8E0 + #FFD8D8E0 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEBD + #FFCCCEBD + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF999999 + #FF999999 + #FF59A8DE + #FF59A8DE + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF444444 + #FF444444 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFA2A4A5 + #FFA2A4A5 + #FFC9DEF5 + #FFC9DEF5 + #FFA2A4A5 + #FFA2A4A5 + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFF6F6F6 + #FFF6F6F6 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF717171 + #FFF5F5F5 + #FFF5F5F5 + #FFCCCEDB + #FFCCCEDB + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF0E6198 + #FF0E6198 + #FFFFFFFF + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFFFFFFF + #FFF7F7F9 + #FFF7F7F9 + #FFF7F7F9 + #FFF7F7F9 + #FF717171 + #FF717171 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF525252 + #FF525252 + #FFEEEEF2 + #FFEEEEF2 + #99525252 + #99525252 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FF0E70C0 + #FF0E70C0 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF0E70C0 + #FF0E70C0 + #FFCCCEDB + #FFCCCEDB + #FF444444 + #FF444444 + #FF1E1E1E + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF705829 + #FF705829 + #FFB0A781 + #FFB0A781 + #FFA19667 + #FFA19667 + #FFA79432 + #FFA79432 + #FFD0D4B7 + #FFD0D4B7 + #FFBFC749 + #FFBFC749 + #FFCAB22D + #FFCAB22D + #FFFBF7C8 + #FFFBF7C8 + #FFE2E442 + #FFE2E442 + #FF5D8039 + #FF5D8039 + #FFB1C97B + #FFB1C97B + #FF9FB861 + #FF9FB861 + #FF8E5478 + #FF8E5478 + #FFE2B1CD + #FFE2B1CD + #FFCB98B6 + #FFCB98B6 + #FFAD1C2B + #FFAD1C2B + #FFFF9F99 + #FFFF9F99 + #FFFF7971 + #FFFF7971 + #FF779AB6 + #FF779AB6 + #FFC6D4DF + #FFC6D4DF + #FFB8CCD7 + #FFB8CCD7 + #FF427094 + #FF427094 + #FFA0B7C9 + #FFA0B7C9 + #FF89ABBD + #FF89ABBD + #FF5386BF + #FF5386BF + #FFB9D4EE + #FFB9D4EE + #FFA1C7E7 + #FFA1C7E7 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FFFFFFFF + #FFFFFFFF + #FF000000 + #FF000000 + + + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.UI/Themes/Light/ThemedDialogColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Light/ThemedDialogColors.xaml new file mode 100644 index 0000000000..1fb82d7fdc --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Light/ThemedDialogColors.xaml @@ -0,0 +1,66 @@ + + #00000000 + #00000000 + #00000000 + #00000000 + #FF1E1E1E + #FF1E1E1E + #FF717171 + #FF717171 + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FF0E70C0 + #FF0E70C0 + #FFA2A4A5 + #FFA2A4A5 + #FF007ACC + #FF007ACC + #FF0E70C0 + #FF0E70C0 + #FFA2A4A5 + #FFA2A4A5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFCCCEDB + #00000000 + #00000000 + #00000000 + #00000000 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FF1E1E1E + #FF1E1E1E + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FF1E1E1E + #FFFBFBFB + #FFFBFBFB + #FF1E1E1E + #FF1E1E1E + #FFEFEFF2 + #FFEFEFF2 + diff --git a/src/GitHub.VisualStudio.UI/Themes/Light/TreeViewColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Light/TreeViewColors.xaml new file mode 100644 index 0000000000..93b53f590d --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Light/TreeViewColors.xaml @@ -0,0 +1,48 @@ + + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFE51400 + #FFE51400 + #FF000000 + #FF000000 + #FF3399FF + #FF3399FF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFD0F7FF + #FFD0F7FF + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF1E1E1E + #FF007ACC + #FF007ACC + #FFE51400 + #FFE51400 + diff --git a/src/GitHub.VisualStudio.UI/Themes/Light/VsBrushes.xaml b/src/GitHub.VisualStudio.UI/Themes/Light/VsBrushes.xaml new file mode 100644 index 0000000000..d4d6c95333 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Light/VsBrushes.xaml @@ -0,0 +1,554 @@ + + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF0E70C0 + #FF444444 + #FFFFFFFF + #FFEEEEF2 + #FFCCCEBD + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFD8D8E0 + #FFCCCEBD + #FF1E1E1E + #FF1E1E1E + #FFF0F2F9 + #FFD3DCEF + #FFCCCC66 + #FFFFFFCC + #FF000000 + #FFD2D2D2 + #FF808080 + #FF000000 + #FFFFFFFF + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFF7F0F0 + #FFEDDADC + #FFFFFFFF + #FF0054E3 + #FFDDD6EF + #FF266035 + #FFFFFFFF + #FF716F64 + #FFF3F7F0 + #FFE6F0DB + #FF808080 + #FF716F64 + #FFB0764F + #FF716F64 + #FF808080 + #FF716F64 + #FFD8D8D8 + #FF808080 + #FF716F64 + #FFD6ECEF + #FFFF0000 + #FFF8F4E9 + #FFF0E9D2 + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FFFFFFFF + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFEEEEF2 + #FF717171 + #FF999999 + #FF999999 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF3399FF + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFF6F6F6 + #FF007ACC + #FFE0E3E6 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFEEEEF2 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FFEEEEF2 + #FF3399FF + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FF1E1E1E + #FFEEEEF2 + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FFFDFBAC + #FF717171 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FFCCCEDB + #FFE7E8EC + #FFCCCEDB + #FFEDEEF0 + #FF1E1E1E + #FFCCCEDB + #FF1E1E1E + #FFD6D8DC + #FFCCCEDB + #FFEDEEF0 + #FFA2A4A5 + #FFCCCEDB + #FFA2A4A5 + #FFF5F5F5 + #FF999999 + #FFCCCEDB + #FFA2A4A5 + #FFFFFFFF + #FFF2F4F8 + #FF000000 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFFFFFFF + #FF000000 + #FFE7E8EC + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FF007ACC + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FFFFFFFF + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #72000000 + #FFCCCEDB + #FFF5F5F5 + #FF0E70C0 + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFFFA300 + #FFFFA300 + #FFA2A4A5 + #FFA2A4A5 + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF1C97EA + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FF1E1E1E + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF717171 + #FFEFEFE2 + #FF1E1E1E + #FFF0F0F0 + #FFDEE1E7 + #FF0066CC + #FF000000 + #FFFFFFFF + #FF0066CC + #FF000000 + #FFFFFFFF + #FFA8B3C2 + #FFFFFFFF + #FFA8B3C2 + #FF000000 + #FFDEE1E7 + #FF000000 + #FFA8B3C2 + #FF000000 + #FFFFFFFF + #FF000000 + #FFDEE1E7 + #FF1B293E + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FF000000 + #FF000000 + #FF3399FF + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFA2A4A5 + #FFFDFBAC + #FF1E1E1E + #FFCCCEDB + #FFF6F6F6 + #FF1E1E1E + #FFF5F5F5 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF3399FF + #FF3399FF + #FFFEFEFE + #FFFEFEFE + #FF1E1E1E + #FFFEFEFE + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFF5F5F5 + #FF1E1E1E + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFFEFEFE + #FFFEFEFE + #FF0E70C0 + #FF007ACC + #FF0E70C0 + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FFF5F5F5 + #FF1E1E1E + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FFEEEEF2 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFFDFBAC + #FFFDFBAC + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFC2C3C9 + #FFC2C3C9 + #FFC2C3C9 + #FF686868 + #FF5B5B5B + #FFFCFCFC + #FFFCFCFC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFE5C365 + #FFFFEFBB + #FFE5C365 + #FFFDFBAC + #FF000000 + #FF000000 + #FF4169E1 + #FF96A9DD + #FFE122DF + #FFF5F5F5 + #FF1E1E1E + #FF999999 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFF30506 + #FF0097FB + #FF55AAFF + #FFF30506 + #FF007ACC + #FFFFFFFF + #FF363639 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF30506 + #FF555555 + #FF007ACC + #FF77AAFF + #FF1E1E1E + #FF999999 + #FF007ACC + #FF555555 + #FF999999 + #FF007ACC + #FF000000 + #FF3F3F3F + #FF464646 + #FF999999 + #FFFFFFFF + #FFF0F0F0 + #FFEEEEF2 + #FFCCCEDB + #FFD8D8E0 + #FFEEEEF2 + #FFCCCEBD + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF444444 + #FFF5F5F5 + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFF5F5F5 + #FFCCCEDB + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFF7F7F9 + #FFF7F7F9 + #FF717171 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FFF5F5F5 + #FF0E70C0 + #FF444444 + #FF1E1E1E + #FF705829 + #FFB0A781 + #FFA19667 + #FFA79432 + #FFD0D4B7 + #FFBFC749 + #FFCAB22D + #FFFBF7C8 + #FFE2E442 + #FF5D8039 + #FFB1C97B + #FF9FB861 + #FF8E5478 + #FFE2B1CD + #FFCB98B6 + #FFAD1C2B + #FFFF9F99 + #FFFF7971 + #FF779AB6 + #FFC6D4DF + #FFB8CCD7 + #FF427094 + #FFA0B7C9 + #FF89ABBD + #FF5386BF + #FFB9D4EE + #FFA1C7E7 + #FFF5F5F5 + #FFEEEEF2 + #FF1E1E1E + #FFFFFFFF + #FF000000 + #00FFFFFF + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.UI/Themes/Light/VsColors.xaml b/src/GitHub.VisualStudio.UI/Themes/Light/VsColors.xaml new file mode 100644 index 0000000000..a485f4f7db --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/Light/VsColors.xaml @@ -0,0 +1,505 @@ + + #FFCCCEDB + #FFCCCEDB + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF0E70C0 + #FF444444 + #FFFFFFFF + #FFEEEEF2 + #FFCCCEBD + #FFF5F5F5 + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFD8D8E0 + #FFCCCEBD + #FF1E1E1E + #FF1E1E1E + #FFF0F2F9 + #FFD3DCEF + #FFCCCC66 + #FFFFFFCC + #FF000000 + #FFD2D2D2 + #FF808080 + #FF000000 + #FFFFFFFF + #FF00008B + #FF000000 + #FF000000 + #FF000000 + #FFFFFFFF + #FFF7F0F0 + #FFEDDADC + #FFFFFFFF + #FF0054E3 + #FFDDD6EF + #FF266035 + #FFFFFFFF + #FF716F64 + #FFF3F7F0 + #FFE6F0DB + #FF808080 + #FF716F64 + #FFB0764F + #FF716F64 + #FF808080 + #FF716F64 + #FFD8D8D8 + #FF808080 + #FF716F64 + #FFD6ECEF + #FFFF0000 + #FFF8F4E9 + #FFF0E9D2 + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FFFFFFFF + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFEEEEF2 + #FF717171 + #FF999999 + #FF999999 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF3399FF + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #FFF6F6F6 + #FF007ACC + #FFE0E3E6 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFEEEEF2 + #FF717171 + #FF007ACC + #FF007ACC + #FF007ACC + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF007ACC + #FFEEEEF2 + #FF3399FF + #FFEEEEF2 + #FF1E1E1E + #FF1E1E1E + #FFA2A4A5 + #FF1E1E1E + #FFEEEEF2 + #FFCCCEDB + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF717171 + #FFFDFBAC + #FF717171 + #FF0E70C0 + #FF0E70C0 + #FF0E70C0 + #FFCCCEDB + #FFE7E8EC + #FFCCCEDB + #FFEDEEF0 + #FF1E1E1E + #FFCCCEDB + #FF1E1E1E + #FFD6D8DC + #FFCCCEDB + #FFEDEEF0 + #FFA2A4A5 + #FFCCCEDB + #FFA2A4A5 + #FFF5F5F5 + #FF999999 + #FFCCCEDB + #FFA2A4A5 + #FFFFFFFF + #FFF2F4F8 + #FF000000 + #FF4A6184 + #FF4A6184 + #FFBCC7D8 + #FFFFFFFF + #FF000000 + #FFE7E8EC + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FF007ACC + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FFFFFFFF + #FF007ACC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FF1E1E1E + #FFF6F6F6 + #FFF6F6F6 + #FFCCCEDB + #72000000 + #FFCCCEDB + #FFF5F5F5 + #FF0E70C0 + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFFFA300 + #FFFFA300 + #FFA2A4A5 + #FFA2A4A5 + #FFEEEEF2 + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FFEEEEF2 + #FFEEEEF2 + #FF1C97EA + #FFD0E6F5 + #FF1C97EA + #FF1C97EA + #FFFFFFFF + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF717171 + #FF007ACC + #FF007ACC + #FFD0E6F5 + #FF0E639C + #FF0E639C + #FF0E639C + #FF0E639C + #FFFFFFFF + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FF1E1E1E + #FF000000 + #FFFFFFFF + #FF000000 + #FFFFFFFF + #FF717171 + #FFEFEFE2 + #FF1E1E1E + #FFF0F0F0 + #FFDEE1E7 + #FF0066CC + #FF000000 + #FFFFFFFF + #FF0066CC + #FF000000 + #FFFFFFFF + #FFA8B3C2 + #FFFFFFFF + #FFA8B3C2 + #FF000000 + #FFDEE1E7 + #FF000000 + #FFA8B3C2 + #FF000000 + #FFFFFFFF + #FF000000 + #FFDEE1E7 + #FF1B293E + #FF0066CC + #FF0066CC + #FFF0F0F0 + #FF000000 + #FF000000 + #FF3399FF + #FFFFFFFF + #FFCCCEDB + #FFEEEEF2 + #FFA2A4A5 + #FFFDFBAC + #FF1E1E1E + #FFCCCEDB + #FFF6F6F6 + #FF1E1E1E + #FFF5F5F5 + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF3399FF + #FF3399FF + #FFFEFEFE + #FFFEFEFE + #FF1E1E1E + #FFFEFEFE + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FF1E1E1E + #FFCCCEDB + #FFF5F5F5 + #FF1E1E1E + #FFFEFEFE + #FFCCCEDB + #FFCCCEDB + #FFCCCEDB + #FF1E1E1E + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFFEFEFE + #FFFEFEFE + #FF0E70C0 + #FF007ACC + #FF0E70C0 + #FFEEEEF2 + #FFEEEEF2 + #FF1E1E1E + #FFF5F5F5 + #FF1E1E1E + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FF3399FF + #FFEEEEF2 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFFDFBAC + #FFFDFBAC + #FF1E1E1E + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFC2C3C9 + #FFC2C3C9 + #FFC2C3C9 + #FF686868 + #FF5B5B5B + #FFFCFCFC + #FFFCFCFC + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FF007ACC + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFE5C365 + #FFFFEFBB + #FFE5C365 + #FFFDFBAC + #FF000000 + #FF000000 + #FF4169E1 + #FF96A9DD + #FFE122DF + #FFF5F5F5 + #FF1E1E1E + #FF999999 + #FF2D2D30 + #FF2D2D30 + #FF999999 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FFF1F1F1 + #FF007ACC + #FF007ACC + #FFF30506 + #FF0097FB + #FF55AAFF + #FFF30506 + #FF007ACC + #FFFFFFFF + #FF363639 + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFFFFFFF + #FFF1F1F1 + #FFF30506 + #FF555555 + #FF007ACC + #FF77AAFF + #FF1E1E1E + #FF999999 + #FF007ACC + #FF555555 + #FF999999 + #FF007ACC + #FF000000 + #FF3F3F3F + #FF464646 + #FF999999 + #FFFFFFFF + #FFF0F0F0 + #FFEEEEF2 + #FFCCCEDB + #FFD8D8E0 + #FFEEEEF2 + #FFCCCEBD + #FFEEEEF2 + #FF007ACC + #FF007ACC + #FF007ACC + #FF007ACC + #FFFFFFFF + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FF444444 + #FFF5F5F5 + #FFCCCEDB + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFF5F5F5 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFF5F5F5 + #FFCCCEDB + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF0E6198 + #FFFFFFFF + #FF52B0EF + #FF52B0EF + #FFFFFFFF + #FFF7F7F9 + #FFF7F7F9 + #FF717171 + #FFF5F5F5 + #FFF5F5F5 + #FF1E1E1E + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFEEEEF2 + #FFC9DEF5 + #FFC9DEF5 + #FFC9DEF5 + #FF1E1E1E + #FFF5F5F5 + #FF0E70C0 + #FF444444 + #FF1E1E1E + #FF705829 + #FFB0A781 + #FFA19667 + #FFA79432 + #FFD0D4B7 + #FFBFC749 + #FFCAB22D + #FFFBF7C8 + #FFE2E442 + #FF5D8039 + #FFB1C97B + #FF9FB861 + #FF8E5478 + #FFE2B1CD + #FFCB98B6 + #FFAD1C2B + #FFFF9F99 + #FFFF7971 + #FF779AB6 + #FFC6D4DF + #FFB8CCD7 + #FF427094 + #FFA0B7C9 + #FF89ABBD + #FF5386BF + #FFB9D4EE + #FFA1C7E7 + #FFF5F5F5 + #FFEEEEF2 + #FF1E1E1E + #FFFFFFFF + #FF000000 + diff --git a/src/GitHub.VisualStudio.UI/Themes/ThemedDialogDefaultStyles.xaml b/src/GitHub.VisualStudio.UI/Themes/ThemedDialogDefaultStyles.xaml new file mode 100644 index 0000000000..7340e9f3ed --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Themes/ThemedDialogDefaultStyles.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GitHub.UI/Controls/HorizontalShadowDivider.xaml b/src/GitHub.VisualStudio.UI/UI/Controls/HorizontalShadowDivider.xaml similarity index 60% rename from src/GitHub.UI/Controls/HorizontalShadowDivider.xaml rename to src/GitHub.VisualStudio.UI/UI/Controls/HorizontalShadowDivider.xaml index 7f3daa680a..a60aca039a 100644 --- a/src/GitHub.UI/Controls/HorizontalShadowDivider.xaml +++ b/src/GitHub.VisualStudio.UI/UI/Controls/HorizontalShadowDivider.xaml @@ -1,27 +1,28 @@ - - + - - - + + + + + - - - + + + + + diff --git a/src/GitHub.UI/Assets/Controls/LightModalViewTabControl.xaml b/src/GitHub.VisualStudio.UI/UI/Controls/LightModalViewTabControl.xaml similarity index 92% rename from src/GitHub.UI/Assets/Controls/LightModalViewTabControl.xaml rename to src/GitHub.VisualStudio.UI/UI/Controls/LightModalViewTabControl.xaml index f0cfb7080a..3b50817815 100644 --- a/src/GitHub.UI/Assets/Controls/LightModalViewTabControl.xaml +++ b/src/GitHub.VisualStudio.UI/UI/Controls/LightModalViewTabControl.xaml @@ -1,5 +1,5 @@  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + - - - - Repository URL or GitHub username and repository - - (hubot/cool-repo) - - - - diff --git a/src/GitHub.VisualStudio.UI/Views/Dialog/Clone/SelectPageView.xaml b/src/GitHub.VisualStudio.UI/Views/Dialog/Clone/SelectPageView.xaml index 4a79d4ee75..ac5c4f11c0 100644 --- a/src/GitHub.VisualStudio.UI/Views/Dialog/Clone/SelectPageView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/Dialog/Clone/SelectPageView.xaml @@ -3,35 +3,31 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" - xmlns:local="clr-namespace:GitHub.VisualStudio.Views.Dialog.Clone" xmlns:ghfvs="https://github.com/github/VisualStudio" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="400"> - - - - - - - - - + + + - + + VirtualizingPanel.IsVirtualizingWhenGrouping="True" + KeyboardNavigation.TabIndex="2"> diff --git a/src/GitHub.VisualStudio.UI/Views/Dialog/Clone/SelectPageView.xaml.cs b/src/GitHub.VisualStudio.UI/Views/Dialog/Clone/SelectPageView.xaml.cs index 32c8457df0..e737eb7acc 100644 --- a/src/GitHub.VisualStudio.UI/Views/Dialog/Clone/SelectPageView.xaml.cs +++ b/src/GitHub.VisualStudio.UI/Views/Dialog/Clone/SelectPageView.xaml.cs @@ -1,6 +1,8 @@ -using System.ComponentModel.Composition; +using System; +using System.ComponentModel.Composition; using System.Windows.Controls; using System.Windows.Input; +using System.Windows.Threading; using GitHub.Exports; using GitHub.ViewModels.Dialog.Clone; @@ -13,6 +15,16 @@ public partial class SelectPageView : UserControl public SelectPageView() { InitializeComponent(); + + // See Douglas Stockwell's suggestion: + // https://social.msdn.microsoft.com/Forums/vstudio/en-US/30ed27ce-f7b7-48ae-8adc-0400b9b9ec78 + IsVisibleChanged += (sender, e) => + { + if (IsVisible) + { + Dispatcher.BeginInvoke(DispatcherPriority.Input, (Action)(() => SearchTextBox.Focus())); + } + }; } protected override void OnPreviewMouseDown(MouseButtonEventArgs e) diff --git a/src/GitHub.VisualStudio.UI/Views/Dialog/DialogStyles.xaml b/src/GitHub.VisualStudio.UI/Views/Dialog/DialogStyles.xaml new file mode 100644 index 0000000000..cea6c08d14 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Views/Dialog/DialogStyles.xaml @@ -0,0 +1,13 @@ + + + + + + + + - - - + + + + + + + + + + @@ -47,7 +46,7 @@ This operation will: - + @@ -95,8 +94,8 @@ Visibility="{Binding Error, Converter={ui:NullToVisibilityConverter}}" HorizontalAlignment="Left" /> - - + - - - - @@ -90,8 +87,6 @@ Grid.Column="1" Grid.Row="3" Content="{x:Static ghfvs:Resources.makePrivateGist}" - Style="{DynamicResource BlueRoundedCheckBox}" - Padding="8,4,0,4" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PrivateGistCheckBox}" /> { } - public class FooBar { } - [ExportViewFor(typeof(IGistCreationViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] public partial class GistCreationView : GenericGistCreationView diff --git a/src/GitHub.VisualStudio.UI/Views/Dialog/GitHubDialogWindow.xaml b/src/GitHub.VisualStudio.UI/Views/Dialog/GitHubDialogWindow.xaml index 2d2b89d700..4530bc66eb 100644 --- a/src/GitHub.VisualStudio.UI/Views/Dialog/GitHubDialogWindow.xaml +++ b/src/GitHub.VisualStudio.UI/Views/Dialog/GitHubDialogWindow.xaml @@ -1,9 +1,10 @@ - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.UI/Views/Dialog/GitHubDialogWindow.xaml.cs b/src/GitHub.VisualStudio.UI/Views/Dialog/GitHubDialogWindow.xaml.cs index 4f09882042..411e0c579a 100644 --- a/src/GitHub.VisualStudio.UI/Views/Dialog/GitHubDialogWindow.xaml.cs +++ b/src/GitHub.VisualStudio.UI/Views/Dialog/GitHubDialogWindow.xaml.cs @@ -1,4 +1,6 @@ using System; +using System.Windows; +using System.Windows.Input; using GitHub.ViewModels.Dialog; using Microsoft.VisualStudio.PlatformUI; @@ -15,5 +17,18 @@ public GitHubDialogWindow(IGitHubDialogWindowViewModel viewModel) viewModel.Done.Subscribe(_ => Close()); InitializeComponent(); } + + void CloseButton_Click(object sender, RoutedEventArgs e) + { + Close(); + } + + void Border_MouseDown(object sender, MouseButtonEventArgs e) + { + if (e.ChangedButton == MouseButton.Left) + { + DragMove(); + } + } } } diff --git a/src/GitHub.VisualStudio.UI/Views/Dialog/LogOutRequiredView.xaml b/src/GitHub.VisualStudio.UI/Views/Dialog/LogOutRequiredView.xaml index 5b8d686c40..161230922b 100644 --- a/src/GitHub.VisualStudio.UI/Views/Dialog/LogOutRequiredView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/Dialog/LogOutRequiredView.xaml @@ -4,19 +4,16 @@ xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ghfvs="https://github.com/github/VisualStudio" - xmlns:prop="clr-namespace:GitHub.VisualStudio.UI;assembly=GitHub.VisualStudio.UI" + xmlns:vs="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0" + Background="{DynamicResource {x:Static vs:ThemedDialogColors.WindowPanelBrushKey}}" + Foreground="{DynamicResource {x:Static vs:ThemedDialogColors.WindowPanelTextBrushKey}}" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - - - - - - - - - - + + + + + - + @@ -53,7 +51,7 @@ - + - - - @@ -87,19 +84,19 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + Style="{DynamicResource InlineValidationMessage}" + ValidatesControl="{Binding ElementName=localPathText}" + ReactiveValidator="{Binding BaseRepositoryPathValidator, Mode=OneWay}" /> - + diff --git a/src/GitHub.VisualStudio.UI/Views/Dialog/RepositoryCreationView.xaml.cs b/src/GitHub.VisualStudio.UI/Views/Dialog/RepositoryCreationView.xaml.cs index 6e1ab5f2d5..722ed81aab 100644 --- a/src/GitHub.VisualStudio.UI/Views/Dialog/RepositoryCreationView.xaml.cs +++ b/src/GitHub.VisualStudio.UI/Views/Dialog/RepositoryCreationView.xaml.cs @@ -42,7 +42,6 @@ public RepositoryCreationView() d(this.OneWayBind(ViewModel, vm => vm.BaseRepositoryPathValidator, v => v.pathValidationMessage.ReactiveValidator)); d(this.BindCommand(ViewModel, vm => vm.CreateRepository, v => v.createRepositoryButton)); - d(this.OneWayBind(ViewModel, vm => vm.IsCreating, v => v.createRepositoryButton.ShowSpinner)); d(this.BindCommand(ViewModel, vm => vm.BrowseForDirectory, v => v.browsePathButton)); diff --git a/src/GitHub.VisualStudio.UI/Views/Dialog/RepositoryRecloneView.xaml b/src/GitHub.VisualStudio.UI/Views/Dialog/RepositoryRecloneView.xaml deleted file mode 100644 index 9ae951cd21..0000000000 --- a/src/GitHub.VisualStudio.UI/Views/Dialog/RepositoryRecloneView.xaml +++ /dev/null @@ -1,160 +0,0 @@ - - - - - - - - https://github.com/github/VisualStudio - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/NoRemoteOriginView.xaml.cs b/src/GitHub.VisualStudio.UI/Views/GitHubPane/NoRemoteOriginView.xaml.cs new file mode 100644 index 0000000000..62610a76c2 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/NoRemoteOriginView.xaml.cs @@ -0,0 +1,21 @@ +using System.ComponentModel.Composition; +using GitHub.UI; +using GitHub.Exports; +using GitHub.ViewModels.GitHubPane; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + public class GenericNoRemoteOriginView : ViewBase + { + } + + [ExportViewFor(typeof(INoRemoteOriginViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class NoRemoteOriginView : GenericNoRemoteOriginView + { + public NoRemoteOriginView() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/NotAGitHubRepositoryView.xaml b/src/GitHub.VisualStudio.UI/Views/GitHubPane/NotAGitHubRepositoryView.xaml index fd53d5f444..e778334fc0 100644 --- a/src/GitHub.VisualStudio.UI/Views/GitHubPane/NotAGitHubRepositoryView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/NotAGitHubRepositoryView.xaml @@ -8,6 +8,8 @@ DataContext="{Binding ViewModel}" d:DesignHeight="300" d:DesignWidth="300" + Background="{DynamicResource GitHubVsToolWindowBackground}" + Foreground="{DynamicResource GitHubVsWindowText}" mc:Ignorable="d"> diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/NotAGitRepositoryView.xaml b/src/GitHub.VisualStudio.UI/Views/GitHubPane/NotAGitRepositoryView.xaml index f1a48ffcd9..c4b11c4f74 100644 --- a/src/GitHub.VisualStudio.UI/Views/GitHubPane/NotAGitRepositoryView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/NotAGitRepositoryView.xaml @@ -8,6 +8,8 @@ DataContext="{Binding ViewModel}" d:DesignHeight="300" d:DesignWidth="300" + Background="{DynamicResource GitHubVsToolWindowBackground}" + Foreground="{DynamicResource GitHubVsWindowText}" mc:Ignorable="d"> diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestAnnotationsView.xaml b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestAnnotationsView.xaml new file mode 100644 index 0000000000..58887eb111 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestAnnotationsView.xaml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + for + + + # + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestAnnotationsView.xaml.cs b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestAnnotationsView.xaml.cs new file mode 100644 index 0000000000..f49803f39e --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestAnnotationsView.xaml.cs @@ -0,0 +1,34 @@ +using System; +using System.ComponentModel.Composition; +using System.Windows.Controls; +using System.Windows.Input; +using GitHub.Exports; +using GitHub.Services; +using GitHub.ViewModels.GitHubPane; +using Microsoft.VisualStudio.Shell; + +namespace GitHub.VisualStudio.Views.GitHubPane +{ + [ExportViewFor(typeof(IPullRequestAnnotationsViewModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class PullRequestAnnotationsView : UserControl + { + public PullRequestAnnotationsView() + { + InitializeComponent(); + } + + void OpenHyperlink(object sender, ExecutedRoutedEventArgs e) + { + Uri uri; + + if (Uri.TryCreate(e.Parameter?.ToString(), UriKind.Absolute, out uri)) + { + Browser.OpenUrl(uri); + } + } + + [Import] + public IVisualStudioBrowser Browser { get; set; } + } +} diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml index d04daeeda1..f018147c27 100644 --- a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCheckView.xaml @@ -7,6 +7,8 @@ xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:cache="clr-namespace:GitHub.UI.Helpers;assembly=GitHub.UI" d:DesignWidth="600" + Background="{DynamicResource GitHubVsToolWindowBackground}" + Foreground="{DynamicResource GitHubVsWindowText}" mc:Ignorable="d"> @@ -18,17 +20,17 @@ + - - - + - + + @@ -38,16 +40,21 @@ - - diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCreationView.xaml b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCreationView.xaml index ba748d1eef..e992e56df0 100644 --- a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCreationView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestCreationView.xaml @@ -9,6 +9,7 @@ d:DesignHeight="450" d:DesignWidth="300" Background="{DynamicResource GitHubVsToolWindowBackground}" + Foreground="{DynamicResource GitHubVsWindowText}" DataContext="{Binding ViewModel}" IsEnabled="{Binding IsBusy, Converter={ghfvs:InverseBooleanConverter}}" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestCreationViewCustom}" @@ -149,18 +150,30 @@ SpellCheck.IsEnabled="True" AutomationProperties.AutomationId="{x:Static ghfvs:AutomationIDs.PullRequestCreationTitleTextBox}"/> - - + + + + + + + + + + + + + - + - - - @@ -82,7 +66,7 @@ - + - + @@ -114,6 +95,89 @@ + + + + View on GitHub + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - View on GitHub - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + ghfvs:ScrollingVerticalStackPanel.IsFixed="true" + Visibility="{Binding Checks.Count, Converter={ghfvs:CountToVisibilityConverter}}"> - + @@ -265,7 +255,7 @@ HeaderText="{Binding Files.ChangedFilesCount, StringFormat={x:Static ghfvs:Resources.ChangesCountFormat}}" Margin="0 8 10 0" ghfvs:ScrollingVerticalStackPanel.IsFixed="true"/> - + diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestFileCommentsView.xaml b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestFileCommentsView.xaml index b8032cc3cc..48741f0dcb 100644 --- a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestFileCommentsView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestFileCommentsView.xaml @@ -5,6 +5,8 @@ xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" + Background="{DynamicResource GitHubVsToolWindowBackground}" + Foreground="{DynamicResource GitHubVsWindowText}" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> @@ -21,7 +23,7 @@ - + diff --git a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestFilesView.xaml b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestFilesView.xaml index 23b8e8499b..81d22b6203 100644 --- a/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestFilesView.xaml +++ b/src/GitHub.VisualStudio.UI/Views/GitHubPane/PullRequestFilesView.xaml @@ -6,6 +6,8 @@ xmlns:ghfvs="https://github.com/github/VisualStudio" xmlns:local="clr-namespace:GitHub.VisualStudio.Views.GitHubPane" xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" + xmlns:catalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog" + xmlns:vsfx="clr-namespace:Microsoft.VisualStudio.Shell;assembly=Microsoft.VisualStudio.Shell.14.0" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" Name="root"> @@ -19,7 +21,8 @@ - + + @@ -98,6 +101,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/GitHub.VisualStudio.UI/Views/IssueishStateBadge.xaml.cs b/src/GitHub.VisualStudio.UI/Views/IssueishStateBadge.xaml.cs new file mode 100644 index 0000000000..0816b464c4 --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Views/IssueishStateBadge.xaml.cs @@ -0,0 +1,12 @@ +using System.Windows.Controls; + +namespace GitHub.VisualStudio.Views +{ + public partial class IssueishStateBadge : UserControl + { + public IssueishStateBadge() + { + InitializeComponent(); + } + } +} diff --git a/src/GitHub.VisualStudio.UI/Views/PullRequestReviewCommentView.xaml b/src/GitHub.VisualStudio.UI/Views/PullRequestReviewCommentView.xaml new file mode 100644 index 0000000000..997056040a --- /dev/null +++ b/src/GitHub.VisualStudio.UI/Views/PullRequestReviewCommentView.xaml @@ -0,0 +1,252 @@ + + + + + You can use a `CompositeDisposable` type here, it's designed to handle disposables in an optimal way (you can just call `Dispose()` on it and it will handle disposing everything it holds). + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +